/****************************************************************************
 *
 * aflatin.c
 *
 *   Auto-fitter hinting routines for latin writing system (body).
 *
 * Copyright (C) 2003-2024 by
 * David Turner, Robert Wilhelm, and Werner Lemberg.
 *
 * This file is part of the FreeType project, and may only be used,
 * modified, and distributed under the terms of the FreeType project
 * license, LICENSE.TXT.  By continuing to use, modify, or distribute
 * this file you indicate that you have read the license and
 * understand and accept it fully.
 *
 */


#include <freetype/ftadvanc.h>
#include <freetype/internal/ftdebug.h>

#include "afglobal.h"
#include "aflatin.h"
#include "aferrors.h"
#include "afadjust.h"


  /**************************************************************************
   *
   * The macro FT_COMPONENT is used in trace mode.  It is an implicit
   * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log
   * messages during execution.
   */
#undef  FT_COMPONENT
#define FT_COMPONENT  aflatin


  /* needed for computation of round vs. flat segments */
#define FLAT_THRESHOLD( x )  ( x / 14 )


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****            L A T I N   G L O B A L   M E T R I C S            *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/


  /* Find segments and links, compute all stem widths, and initialize */
  /* standard width and height for the glyph with given charcode.     */

  FT_LOCAL_DEF( void )
  af_latin_metrics_init_widths( AF_LatinMetrics  metrics,
                                FT_Face          face )
  {
    /* scan the array of segments in each direction */
    AF_GlyphHintsRec  hints[1];


    FT_TRACE5(( "\n" ));
    FT_TRACE5(( "latin standard widths computation (style `%s')\n",
                af_style_names[metrics->root.style_class->style] ));
    FT_TRACE5(( "=====================================================\n" ));
    FT_TRACE5(( "\n" ));

    af_glyph_hints_init( hints, face->memory );

    metrics->axis[AF_DIMENSION_HORZ].width_count = 0;
    metrics->axis[AF_DIMENSION_VERT].width_count = 0;

    {
      FT_Error            error;
      FT_ULong            glyph_index;
      int                 dim;
      AF_LatinMetricsRec  dummy[1];
      AF_Scaler           scaler = &dummy->root.scaler;

      AF_StyleClass   style_class  = metrics->root.style_class;
      AF_ScriptClass  script_class = af_script_classes[style_class->script];

      /* If HarfBuzz is not available, we need a pointer to a single */
      /* unsigned long value.                                        */
      FT_ULong  shaper_buf_;
      void*     shaper_buf = &shaper_buf_;

      const char*  p;

#ifdef FT_DEBUG_LEVEL_TRACE
      FT_ULong  ch = 0;
#endif


      p = script_class->standard_charstring;

      if ( ft_hb_enabled ( metrics->root.globals ) )
        shaper_buf = af_shaper_buf_create( metrics->root.globals );

      /*
       * We check a list of standard characters to catch features like
       * `c2sc' (small caps from caps) that don't contain lowercase letters
       * by definition, or other features that mainly operate on numerals.
       * The first match wins.
       */

      glyph_index = 0;
      while ( *p )
      {
        unsigned int  num_idx;

#ifdef FT_DEBUG_LEVEL_TRACE
        const char*  p_old;
#endif


        while ( *p == ' ' )
          p++;

#ifdef FT_DEBUG_LEVEL_TRACE
        p_old = p;
        GET_UTF8_CHAR( ch, p_old );
#endif

        /* reject input that maps to more than a single glyph */
        p = af_shaper_get_cluster( p, &metrics->root, shaper_buf, &num_idx );
        if ( num_idx > 1 )
          continue;

        /* otherwise exit loop if we have a result */
        glyph_index = af_shaper_get_elem( &metrics->root,
                                          shaper_buf,
                                          0,
                                          NULL,
                                          NULL );
        if ( glyph_index )
          break;
      }

      af_shaper_buf_destroy( metrics->root.globals, shaper_buf );

      if ( !glyph_index )
      {
        FT_TRACE5(( "standard character missing;"
                    " using fallback stem widths\n" ));
        goto Exit;
      }

      FT_TRACE5(( "standard character: U+%04lX (glyph index %lu)\n",
                  ch, glyph_index ));

      error = FT_Load_Glyph( face, glyph_index, FT_LOAD_NO_SCALE );
      if ( error || face->glyph->outline.n_points <= 0 )
        goto Exit;

      FT_ZERO( dummy );

      dummy->units_per_em = metrics->units_per_em;

      scaler->x_scale = 0x10000L;
      scaler->y_scale = 0x10000L;
      scaler->x_delta = 0;
      scaler->y_delta = 0;

      scaler->face        = face;
      scaler->render_mode = FT_RENDER_MODE_NORMAL;
      scaler->flags       = 0;

      af_glyph_hints_rescale( hints, (AF_StyleMetrics)dummy );

      error = af_glyph_hints_reload( hints, &face->glyph->outline );
      if ( error )
        goto Exit;

      for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
      {
        AF_LatinAxis  axis    = &metrics->axis[dim];
        AF_AxisHints  axhints = &hints->axis[dim];
        AF_Segment    seg, limit, link;
        FT_UInt       num_widths = 0;


        error = af_latin_hints_compute_segments( hints,
                                                 (AF_Dimension)dim );
        if ( error )
          goto Exit;

        /*
         * We assume that the glyphs selected for the stem width
         * computation are `featureless' enough so that the linking
         * algorithm works fine without adjustments of its scoring
         * function.
         */
        af_latin_hints_link_segments( hints,
                                      0,
                                      NULL,
                                      (AF_Dimension)dim );

        seg   = axhints->segments;
        limit = FT_OFFSET( seg, axhints->num_segments );

        for ( ; seg < limit; seg++ )
        {
          link = seg->link;

          /* we only consider stem segments there! */
          if ( link && link->link == seg && link > seg )
          {
            FT_Pos  dist;


            dist = seg->pos - link->pos;
            if ( dist < 0 )
              dist = -dist;

            if ( num_widths < AF_LATIN_MAX_WIDTHS )
              axis->widths[num_widths++].org = dist;
          }
        }

        /* this also replaces multiple almost identical stem widths */
        /* with a single one (the value 100 is heuristic)           */
        af_sort_and_quantize_widths( &num_widths, axis->widths,
                                     dummy->units_per_em / 100 );
        axis->width_count = num_widths;
      }

    Exit:
      for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
      {
        AF_LatinAxis  axis = &metrics->axis[dim];
        FT_Pos        stdw;


        stdw = ( axis->width_count > 0 ) ? axis->widths[0].org
                                         : AF_LATIN_CONSTANT( metrics, 50 );

        /* let's try 20% of the smallest width */
        axis->edge_distance_threshold = stdw / 5;
        axis->standard_width          = stdw;
        axis->extra_light             = 0;

#ifdef FT_DEBUG_LEVEL_TRACE
        {
          FT_UInt  i;


          FT_TRACE5(( "%s widths:\n",
                      dim == AF_DIMENSION_VERT ? "horizontal"
                                               : "vertical" ));

          FT_TRACE5(( "  %ld (standard)", axis->standard_width ));
          for ( i = 1; i < axis->width_count; i++ )
            FT_TRACE5(( " %ld", axis->widths[i].org ));

          FT_TRACE5(( "\n" ));
        }
#endif
      }
    }

    FT_TRACE5(( "\n" ));

    af_glyph_hints_done( hints );
  }


  static void
  af_latin_sort_blue( FT_UInt        count,
                      AF_LatinBlue*  table )
  {
    FT_UInt       i, j;
    AF_LatinBlue  swap;


    /* we sort from bottom to top */
    for ( i = 1; i < count; i++ )
    {
      for ( j = i; j > 0; j-- )
      {
        FT_Pos  a, b;


        if ( table[j - 1]->flags & ( AF_LATIN_BLUE_TOP     |
                                     AF_LATIN_BLUE_SUB_TOP ) )
          a = table[j - 1]->ref.org;
        else
          a = table[j - 1]->shoot.org;

        if ( table[j]->flags & ( AF_LATIN_BLUE_TOP     |
                                 AF_LATIN_BLUE_SUB_TOP ) )
          b = table[j]->ref.org;
        else
          b = table[j]->shoot.org;

        if ( b >= a )
          break;

        swap         = table[j];
        table[j]     = table[j - 1];
        table[j - 1] = swap;
      }
    }
  }


  /* Find all blue zones.  Flat segments give the reference points, */
  /* round segments the overshoot positions.                        */

  static int
  af_latin_metrics_init_blues( AF_LatinMetrics  metrics,
                               FT_Face          face )
  {
    FT_Pos        flats [AF_BLUE_STRING_MAX_LEN];
    FT_Pos        rounds[AF_BLUE_STRING_MAX_LEN];

    FT_UInt       num_flats;
    FT_UInt       num_rounds;

    AF_LatinBlue  blue;
    FT_Error      error;
    AF_LatinAxis  axis = &metrics->axis[AF_DIMENSION_VERT];
    FT_Outline    outline;

    AF_StyleClass  sc = metrics->root.style_class;

    AF_Blue_Stringset         bss = sc->blue_stringset;
    const AF_Blue_StringRec*  bs  = &af_blue_stringsets[bss];

    FT_Pos  flat_threshold = FLAT_THRESHOLD( metrics->units_per_em );

    /* If HarfBuzz is not available, we need a pointer to a single */
    /* unsigned long value.                                        */
    FT_ULong  shaper_buf_;
    void*     shaper_buf = &shaper_buf_;


    /* we walk over the blue character strings as specified in the */
    /* style's entry in the `af_blue_stringset' array              */

    FT_TRACE5(( "latin blue zones computation\n" ));
    FT_TRACE5(( "============================\n" ));
    FT_TRACE5(( "\n" ));

    if ( ft_hb_enabled ( metrics->root.globals ) )
      shaper_buf = af_shaper_buf_create( metrics->root.globals );

    for ( ; bs->string != AF_BLUE_STRING_MAX; bs++ )
    {
      const char*  p = &af_blue_strings[bs->string];
      FT_Pos*      blue_ref;
      FT_Pos*      blue_shoot;
      FT_Pos       ascender;
      FT_Pos       descender;


#ifdef FT_DEBUG_LEVEL_TRACE
      {
        FT_Bool  have_flag = 0;


        FT_TRACE5(( "blue zone %u", axis->blue_count ));

        if ( bs->properties )
        {
          FT_TRACE5(( " (" ));

          if ( AF_LATIN_IS_TOP_BLUE( bs ) )
          {
            FT_TRACE5(( "top" ));
            have_flag = 1;
          }
          else if ( AF_LATIN_IS_SUB_TOP_BLUE( bs ) )
          {
            FT_TRACE5(( "sub top" ));
            have_flag = 1;
          }

          if ( AF_LATIN_IS_NEUTRAL_BLUE( bs ) )
          {
            if ( have_flag )
              FT_TRACE5(( ", " ));
            FT_TRACE5(( "neutral" ));
            have_flag = 1;
          }

          if ( AF_LATIN_IS_X_HEIGHT_BLUE( bs ) )
          {
            if ( have_flag )
              FT_TRACE5(( ", " ));
            FT_TRACE5(( "small top" ));
            have_flag = 1;
          }

          if ( AF_LATIN_IS_LONG_BLUE( bs ) )
          {
            if ( have_flag )
              FT_TRACE5(( ", " ));
            FT_TRACE5(( "long" ));
          }

          if ( AF_LATIN_IS_CAPITAL_BOTTOM_BLUE( bs ) )
          {
            if ( have_flag )
              FT_TRACE5(( ", " ));
            FT_TRACE5(( "capital bottom" ));
          }

          if ( AF_LATIN_IS_SMALL_BOTTOM_BLUE( bs ) )
          {
            if ( have_flag )
              FT_TRACE5(( ", " ));
            FT_TRACE5(( "small bottom" ));
          }

          FT_TRACE5(( ")" ));
        }

        FT_TRACE5(( ":\n" ));
      }
#endif /* FT_DEBUG_LEVEL_TRACE */

      num_flats  = 0;
      num_rounds = 0;
      ascender   = 0;
      descender  = 0;

      while ( *p )
      {
        FT_ULong    glyph_index;
        FT_Long     y_offset;
        FT_Int      best_point, best_contour_first, best_contour_last;
        FT_Vector*  points;

        FT_Pos   best_y_extremum;                      /* same as points.y */
        FT_Bool  best_round = 0;

        unsigned int  i, num_idx;

#ifdef FT_DEBUG_LEVEL_TRACE
        const char*  p_old;
        FT_ULong     ch;
#endif


        while ( *p == ' ' )
          p++;

#ifdef FT_DEBUG_LEVEL_TRACE
        p_old = p;
        GET_UTF8_CHAR( ch, p_old );
#endif

        p = af_shaper_get_cluster( p, &metrics->root, shaper_buf, &num_idx );

        if ( !num_idx )
        {
          FT_TRACE5(( "  U+%04lX unavailable\n", ch ));
          continue;
        }

        if ( AF_LATIN_IS_TOP_BLUE( bs ) )
          best_y_extremum = FT_LONG_MIN;
        else
          best_y_extremum = FT_LONG_MAX;

        /* iterate over all glyph elements of the character cluster */
        /* and get the data of the `biggest' one                    */
        for ( i = 0; i < num_idx; i++ )
        {
          FT_Pos   best_y;
          FT_Bool  round = 0;


          /* load the character in the face -- skip unknown or empty ones */
          glyph_index = af_shaper_get_elem( &metrics->root,
                                            shaper_buf,
                                            i,
                                            NULL,
                                            &y_offset );
          if ( glyph_index == 0 )
          {
            FT_TRACE5(( "  U+%04lX unavailable\n", ch ));
            continue;
          }

          error   = FT_Load_Glyph( face, glyph_index, FT_LOAD_NO_SCALE );
          outline = face->glyph->outline;
          /* reject glyphs that don't produce any rendering */
          if ( error || outline.n_points <= 2 )
          {
#ifdef FT_DEBUG_LEVEL_TRACE
            if ( num_idx == 1 )
              FT_TRACE5(( "  U+%04lX contains no (usable) outlines\n", ch ));
            else
              FT_TRACE5(( "  component %u of cluster starting with U+%04lX"
                          " contains no (usable) outlines\n", i, ch ));
#endif
            continue;
          }

          /* now compute min or max point indices and coordinates */
          points             = outline.points;
          best_point         = -1;
          best_contour_first = -1;
          best_contour_last  = -1;
          best_y             = 0;  /* make compiler happy */

          {
            FT_Int  nn;
            FT_Int  pp, first, last;


            last = -1;
            for ( nn = 0; nn < outline.n_contours; nn++ )
            {
              first = last + 1;
              last  = outline.contours[nn];

              /* Avoid single-point contours since they are never      */
              /* rasterized.  In some fonts, they correspond to mark   */
              /* attachment points that are way outside of the glyph's */
              /* real outline.                                         */
              if ( last <= first )
                continue;

              if ( AF_LATIN_IS_TOP_BLUE( bs )     ||
                   AF_LATIN_IS_SUB_TOP_BLUE( bs ) )
              {
                for ( pp = first; pp <= last; pp++ )
                {
                  if ( best_point < 0 || points[pp].y > best_y )
                  {
                    best_point = pp;
                    best_y     = points[pp].y;
                    ascender   = FT_MAX( ascender, best_y + y_offset );
                  }
                  else
                    descender = FT_MIN( descender, points[pp].y + y_offset );
                }
              }
              else
              {
                for ( pp = first; pp <= last; pp++ )
                {
                  if ( best_point < 0 || points[pp].y < best_y )
                  {
                    best_point = pp;
                    best_y     = points[pp].y;
                    descender  = FT_MIN( descender, best_y + y_offset );
                  }
                  else
                    ascender = FT_MAX( ascender, points[pp].y + y_offset );
                }
              }

              if ( best_point > best_contour_last )
              {
                best_contour_first = first;
                best_contour_last  = last;
              }
            }
          }

          /* now check whether the point belongs to a straight or round   */
          /* segment; we first need to find in which contour the extremum */
          /* lies, then inspect its previous and next points              */
          if ( best_point >= 0 )
          {
            FT_Pos  best_x = points[best_point].x;
            FT_Int  prev, next;
            FT_Int  best_segment_first, best_segment_last;
            FT_Int  best_on_point_first, best_on_point_last;
            FT_Pos  dist;


            best_segment_first = best_point;
            best_segment_last  = best_point;

            if ( FT_CURVE_TAG( outline.tags[best_point] ) == FT_CURVE_TAG_ON )
            {
              best_on_point_first = best_point;
              best_on_point_last  = best_point;
            }
            else
            {
              best_on_point_first = -1;
              best_on_point_last  = -1;
            }

            /* look for the previous and next points on the contour  */
            /* that are not on the same Y coordinate, then threshold */
            /* the `closeness'...                                    */
            prev = best_point;
            next = prev;

            do
            {
              if ( prev > best_contour_first )
                prev--;
              else
                prev = best_contour_last;

              dist = FT_ABS( points[prev].y - best_y );
              /* accept a small distance or a small angle (both values are */
              /* heuristic; value 20 corresponds to approx. 2.9 degrees)   */
              if ( dist > 5 )
                if ( FT_ABS( points[prev].x - best_x ) <= 20 * dist )
                  break;

              best_segment_first = prev;

              if ( FT_CURVE_TAG( outline.tags[prev] ) == FT_CURVE_TAG_ON )
              {
                best_on_point_first = prev;
                if ( best_on_point_last < 0 )
                  best_on_point_last = prev;
              }

            } while ( prev != best_point );

            do
            {
              if ( next < best_contour_last )
                next++;
              else
                next = best_contour_first;

              dist = FT_ABS( points[next].y - best_y );
              if ( dist > 5 )
                if ( FT_ABS( points[next].x - best_x ) <= 20 * dist )
                  break;

              best_segment_last = next;

              if ( FT_CURVE_TAG( outline.tags[next] ) == FT_CURVE_TAG_ON )
              {
                best_on_point_last = next;
                if ( best_on_point_first < 0 )
                  best_on_point_first = next;
              }

            } while ( next != best_point );

            if ( AF_LATIN_IS_LONG_BLUE( bs ) )
            {
              /* If this flag is set, we have an additional constraint to  */
              /* get the blue zone distance: Find a segment of the topmost */
              /* (or bottommost) contour that is longer than a heuristic   */
              /* threshold.  This ensures that small bumps in the outline  */
              /* are ignored (for example, the `vertical serifs' found in  */
              /* many Hebrew glyph designs).                               */

              /* If this segment is long enough, we are done.  Otherwise,  */
              /* search the segment next to the extremum that is long      */
              /* enough, has the same direction, and a not too large       */
              /* vertical distance from the extremum.  Note that the       */
              /* algorithm doesn't check whether the found segment is      */
              /* actually the one (vertically) nearest to the extremum.    */

              /* heuristic threshold value */
              FT_Pos  length_threshold = metrics->units_per_em / 25;


              dist = FT_ABS( points[best_segment_last].x -
                               points[best_segment_first].x );

              if ( dist < length_threshold                       &&
                   best_segment_last - best_segment_first + 2 <=
                     best_contour_last - best_contour_first      )
              {
                /* heuristic threshold value */
                FT_Pos  height_threshold = metrics->units_per_em / 4;

                FT_Int   first;
                FT_Int   last;
                FT_Bool  hit;

                /* we intentionally declare these two variables        */
                /* outside of the loop since various compilers emit    */
                /* incorrect warning messages otherwise, talking about */
                /* `possibly uninitialized variables'                  */
                FT_Int  p_first = 0;            /* make compiler happy */
                FT_Int  p_last  = 0;

                FT_Bool  left2right;


                /* compute direction */
                prev = best_point;

                do
                {
                  if ( prev > best_contour_first )
                    prev--;
                  else
                    prev = best_contour_last;

                  if ( points[prev].x != best_x )
                    break;

                } while ( prev != best_point );

                /* skip glyph for the degenerate case */
                if ( prev == best_point )
                  continue;

                left2right = FT_BOOL( points[prev].x < points[best_point].x );

                first = best_segment_last;
                last  = first;
                hit   = 0;

                do
                {
                  FT_Bool  l2r;
                  FT_Pos   d;


                  if ( !hit )
                  {
                    /* no hit; adjust first point */
                    first = last;

                    /* also adjust first and last on point */
                    if ( FT_CURVE_TAG( outline.tags[first] ) ==
                           FT_CURVE_TAG_ON )
                    {
                      p_first = first;
                      p_last  = first;
                    }
                    else
                    {
                      p_first = -1;
                      p_last  = -1;
                    }

                    hit = 1;
                  }

                  if ( last < best_contour_last )
                    last++;
                  else
                    last = best_contour_first;

                  if ( FT_ABS( best_y - points[first].y ) > height_threshold )
                  {
                    /* vertical distance too large */
                    hit = 0;
                    continue;
                  }

                  /* same test as above */
                  dist = FT_ABS( points[last].y - points[first].y );
                  if ( dist > 5 )
                    if ( FT_ABS( points[last].x - points[first].x ) <=
                           20 * dist )
                    {
                      hit = 0;
                      continue;
                    }

                  if ( FT_CURVE_TAG( outline.tags[last] ) == FT_CURVE_TAG_ON )
                  {
                    p_last = last;
                    if ( p_first < 0 )
                      p_first = last;
                  }

                  l2r = FT_BOOL( points[first].x < points[last].x );
                  d   = FT_ABS( points[last].x - points[first].x );

                  if ( l2r == left2right     &&
                       d >= length_threshold )
                  {
                    /* all constraints are met; update segment after */
                    /* finding its end                               */
                    do
                    {
                      if ( last < best_contour_last )
                        last++;
                      else
                        last = best_contour_first;

                      d = FT_ABS( points[last].y - points[first].y );
                      if ( d > 5 )
                        if ( FT_ABS( points[next].x - points[first].x ) <=
                               20 * dist )
                        {
                          if ( last > best_contour_first )
                            last--;
                          else
                            last = best_contour_last;
                          break;
                        }

                      p_last = last;

                      if ( FT_CURVE_TAG( outline.tags[last] ) ==
                             FT_CURVE_TAG_ON )
                      {
                        p_last = last;
                        if ( p_first < 0 )
                          p_first = last;
                      }

                    } while ( last != best_segment_first );

                    best_y = points[first].y;

                    best_segment_first = first;
                    best_segment_last  = last;

                    best_on_point_first = p_first;
                    best_on_point_last  = p_last;

                    break;
                  }

                } while ( last != best_segment_first );
              }
            }

            /* for computing blue zones, we add the y offset as returned */
            /* by the currently used OpenType feature -- for example,    */
            /* superscript glyphs might be identical to subscript glyphs */
            /* with a vertical shift                                     */
            best_y += y_offset;

#ifdef FT_DEBUG_LEVEL_TRACE
            if ( num_idx == 1 )
              FT_TRACE5(( "  U+%04lX: best_y = %5ld", ch, best_y ));
            else
              FT_TRACE5(( "  component %u of cluster starting with U+%04lX:"
                          " best_y = %5ld", i, ch, best_y ));
#endif

            /* now set the `round' flag depending on the segment's kind: */
            /*                                                           */
            /* - if the horizontal distance between the first and last   */
            /*   `on' point is larger than a heuristic threshold         */
            /*   we have a flat segment                                  */
            /* - if either the first or the last point of the segment is */
            /*   an `off' point, the segment is round, otherwise it is   */
            /*   flat                                                    */
            if ( best_on_point_first >= 0                               &&
                 best_on_point_last >= 0                                &&
                 ( FT_ABS( points[best_on_point_last].x -
                           points[best_on_point_first].x ) ) >
                   flat_threshold                                       )
              round = 0;
            else
              round = FT_BOOL(
                        FT_CURVE_TAG( outline.tags[best_segment_first] ) !=
                          FT_CURVE_TAG_ON                                   ||
                        FT_CURVE_TAG( outline.tags[best_segment_last]  ) !=
                          FT_CURVE_TAG_ON                                   );

            if ( round && AF_LATIN_IS_NEUTRAL_BLUE( bs ) )
            {
              /* only use flat segments for a neutral blue zone */
              FT_TRACE5(( " (round, skipped)\n" ));
              continue;
            }

            FT_TRACE5(( " (%s)\n", round ? "round" : "flat" ));
          }

          if ( AF_LATIN_IS_TOP_BLUE( bs ) )
          {
            if ( best_y > best_y_extremum )
            {
              best_y_extremum = best_y;
              best_round      = round;
            }
          }
          else
          {
            if ( best_y < best_y_extremum )
            {
              best_y_extremum = best_y;
              best_round      = round;
            }
          }

        } /* end for loop */

        if ( !( best_y_extremum == FT_LONG_MIN ||
                best_y_extremum == FT_LONG_MAX ) )
        {
          if ( best_round )
            rounds[num_rounds++] = best_y_extremum;
          else
            flats[num_flats++]   = best_y_extremum;
        }

      } /* end while loop */

      if ( num_flats == 0 && num_rounds == 0 )
      {
        /*
         * we couldn't find a single glyph to compute this blue zone,
         * we will simply ignore it then
         */
        FT_TRACE5(( "  empty\n" ));
        continue;
      }

      /* we have computed the contents of the `rounds' and `flats' tables, */
      /* now determine the reference and overshoot position of the blue -- */
      /* we simply take the median value after a simple sort               */
      af_sort_pos( num_rounds, rounds );
      af_sort_pos( num_flats,  flats );

      blue       = &axis->blues[axis->blue_count];
      blue_ref   = &blue->ref.org;
      blue_shoot = &blue->shoot.org;

      axis->blue_count++;

      if ( num_flats == 0 )
      {
        *blue_ref   =
        *blue_shoot = rounds[num_rounds / 2];
      }
      else if ( num_rounds == 0 )
      {
        *blue_ref   =
        *blue_shoot = flats[num_flats / 2];
      }
      else
      {
        *blue_ref   = flats [num_flats  / 2];
        *blue_shoot = rounds[num_rounds / 2];
      }

      /* there are sometimes problems: if the overshoot position of top     */
      /* zones is under its reference position, or the opposite for bottom  */
      /* zones.  We must thus check everything there and correct the errors */
      if ( *blue_shoot != *blue_ref )
      {
        FT_Pos   ref      = *blue_ref;
        FT_Pos   shoot    = *blue_shoot;
        FT_Bool  over_ref = FT_BOOL( shoot > ref );


        if ( ( AF_LATIN_IS_TOP_BLUE( bs )    ||
               AF_LATIN_IS_SUB_TOP_BLUE( bs) ) ^ over_ref )
        {
          *blue_ref   =
          *blue_shoot = ( shoot + ref ) / 2;

          FT_TRACE5(( "  [overshoot smaller than reference,"
                      " taking mean value]\n" ));
        }
      }

      blue->ascender  = ascender;
      blue->descender = descender;

      blue->flags = 0;
      if ( AF_LATIN_IS_TOP_BLUE( bs ) )
        blue->flags |= AF_LATIN_BLUE_TOP;
      if ( AF_LATIN_IS_SUB_TOP_BLUE( bs ) )
        blue->flags |= AF_LATIN_BLUE_SUB_TOP;
      if ( AF_LATIN_IS_NEUTRAL_BLUE( bs ) )
        blue->flags |= AF_LATIN_BLUE_NEUTRAL;
      if ( AF_LATIN_IS_CAPITAL_BOTTOM_BLUE( bs ) )
        blue->flags |= AF_LATIN_BLUE_BOTTOM;
      if ( AF_LATIN_IS_SMALL_BOTTOM_BLUE( bs ) )
        blue->flags |= AF_LATIN_BLUE_BOTTOM_SMALL;

      /*
       * The following flag is used later to adjust the y and x scales
       * in order to optimize the pixel grid alignment of the top of small
       * letters.
       */
      if ( AF_LATIN_IS_X_HEIGHT_BLUE( bs ) )
        blue->flags |= AF_LATIN_BLUE_ADJUSTMENT;

      FT_TRACE5(( "    -> reference = %ld\n", *blue_ref ));
      FT_TRACE5(( "       overshoot = %ld\n", *blue_shoot ));

    } /* end for loop */

    af_shaper_buf_destroy( metrics->root.globals, shaper_buf );

    if ( axis->blue_count )
    {
      /* we finally check whether blue zones are ordered;            */
      /* `ref' and `shoot' values of two blue zones must not overlap */

      FT_UInt       i;
      AF_LatinBlue  blue_sorted[AF_BLUE_STRINGSET_MAX_LEN];


      for ( i = 0; i < axis->blue_count; i++ )
        blue_sorted[i] = &axis->blues[i];

      /* sort bottoms of blue zones... */
      af_latin_sort_blue( axis->blue_count, blue_sorted );

      /* ...and adjust top values if necessary */
      for ( i = 0; i < axis->blue_count - 1; i++ )
      {
        FT_Pos*  a;
        FT_Pos*  b;

#ifdef FT_DEBUG_LEVEL_TRACE
        FT_Bool  a_is_top = 0;
#endif


        if ( blue_sorted[i]->flags & ( AF_LATIN_BLUE_TOP     |
                                       AF_LATIN_BLUE_SUB_TOP ) )
        {
          a = &blue_sorted[i]->shoot.org;
#ifdef FT_DEBUG_LEVEL_TRACE
          a_is_top = 1;
#endif
        }
        else
          a = &blue_sorted[i]->ref.org;

        if ( blue_sorted[i + 1]->flags & ( AF_LATIN_BLUE_TOP     |
                                           AF_LATIN_BLUE_SUB_TOP ) )
          b = &blue_sorted[i + 1]->shoot.org;
        else
          b = &blue_sorted[i + 1]->ref.org;

        if ( *a > *b )
        {
          *a = *b;
          FT_TRACE5(( "blue zone overlap:"
                      " adjusting %s %td to %ld\n",
                      a_is_top ? "overshoot" : "reference",
                      blue_sorted[i] - axis->blues,
                      *a ));
        }
      }

      FT_TRACE5(( "\n" ));

      return 0;
    }
    else
    {
      /* disable hinting for the current style if there are no blue zones */

      AF_FaceGlobals  globals = metrics->root.globals;
      FT_UShort*      gstyles = globals->glyph_styles;

      FT_UInt  i;


      FT_TRACE5(( "no blue zones found:"
                  " hinting disabled for this style\n" ));

      for ( i = 0; i < globals->glyph_count; i++ )
      {
        if ( ( gstyles[i] & AF_STYLE_MASK ) == sc->style )
          gstyles[i] = AF_STYLE_NONE_DFLT;
      }

      FT_TRACE5(( "\n" ));

      return 1;
    }
  }


  /* Check whether all ASCII digits have the same advance width. */

  FT_LOCAL_DEF( void )
  af_latin_metrics_check_digits( AF_LatinMetrics  metrics,
                                 FT_Face          face )
  {
    FT_Bool  started = 0, same_width = 1;
    FT_Long  advance = 0, old_advance = 0;

    /* If HarfBuzz is not available, we need a pointer to a single */
    /* unsigned long value.                                        */
    FT_ULong  shaper_buf_;
    void*     shaper_buf = &shaper_buf_;

    /* in all supported charmaps, digits have character codes 0x30-0x39 */
    const char   digits[] = "0 1 2 3 4 5 6 7 8 9";
    const char*  p;

    FT_UNUSED( face );


    p = digits;

    if ( ft_hb_enabled ( metrics->root.globals ) )
      shaper_buf = af_shaper_buf_create( metrics->root.globals );

    while ( *p )
    {
      FT_ULong      glyph_index;
      unsigned int  num_idx;


      /* reject input that maps to more than a single glyph */
      p = af_shaper_get_cluster( p, &metrics->root, shaper_buf, &num_idx );
      if ( num_idx > 1 )
        continue;

      glyph_index = af_shaper_get_elem( &metrics->root,
                                        shaper_buf,
                                        0,
                                        &advance,
                                        NULL );
      if ( !glyph_index )
        continue;

      if ( started )
      {
        if ( advance != old_advance )
        {
          same_width = 0;
          break;
        }
      }
      else
      {
        old_advance = advance;
        started     = 1;
      }
    }

    af_shaper_buf_destroy( metrics->root.globals, shaper_buf );

    metrics->root.digits_have_same_width = same_width;
  }


  /* Initialize global metrics. */

  FT_LOCAL_DEF( FT_Error )
  af_latin_metrics_init( AF_StyleMetrics  metrics_,   /* AF_LatinMetrics */
                         FT_Face          face )
  {
    AF_LatinMetrics  metrics = (AF_LatinMetrics)metrics_;

    FT_Error  error = FT_Err_Ok;

    FT_CharMap  oldmap = face->charmap;


    metrics->units_per_em = face->units_per_EM;

    if ( !FT_Select_Charmap( face, FT_ENCODING_UNICODE ) )
    {
      af_latin_metrics_init_widths( metrics, face );
      if ( af_latin_metrics_init_blues( metrics, face ) )
      {
        /* use internal error code to indicate missing blue zones */
        error = -1;
        goto Exit;
      }
      af_latin_metrics_check_digits( metrics, face );
    }

    af_reverse_character_map_new( &metrics->root.reverse_charmap,
                                  &metrics->root );

  Exit:
    face->charmap = oldmap;
    return error;
  }


  /* Adjust scaling value, then scale and shift widths   */
  /* and blue zones (if applicable) for given dimension. */

  static void
  af_latin_metrics_scale_dim( AF_LatinMetrics  metrics,
                              AF_Scaler        scaler,
                              AF_Dimension     dim )
  {
    FT_Fixed      scale;
    FT_Pos        delta;
    AF_LatinAxis  axis;
    FT_UInt       nn;


    if ( dim == AF_DIMENSION_HORZ )
    {
      scale = scaler->x_scale;
      delta = scaler->x_delta;
    }
    else
    {
      scale = scaler->y_scale;
      delta = scaler->y_delta;
    }

    axis = &metrics->axis[dim];

    if ( axis->org_scale == scale && axis->org_delta == delta )
      return;

    axis->org_scale = scale;
    axis->org_delta = delta;

    /*
     * correct X and Y scale to optimize the alignment of the top of small
     * letters to the pixel grid
     */
    {
      AF_LatinAxis  Axis = &metrics->axis[AF_DIMENSION_VERT];
      AF_LatinBlue  blue = NULL;


      for ( nn = 0; nn < Axis->blue_count; nn++ )
      {
        if ( Axis->blues[nn].flags & AF_LATIN_BLUE_ADJUSTMENT )
        {
          blue = &Axis->blues[nn];
          break;
        }
      }

      if ( blue )
      {
        FT_Pos   scaled;
        FT_Pos   threshold;
        FT_Pos   fitted;
        FT_UInt  limit;
        FT_UInt  ppem;


        scaled    = FT_MulFix( blue->shoot.org, scale );
        ppem      = metrics->root.scaler.face->size->metrics.x_ppem;
        limit     = metrics->root.globals->increase_x_height;
        threshold = 40;

        /* if the `increase-x-height' property is active, */
        /* we round up much more often                    */
        if ( limit                                 &&
             ppem <= limit                         &&
             ppem >= AF_PROP_INCREASE_X_HEIGHT_MIN )
          threshold = 52;

        fitted = ( scaled + threshold ) & ~63;

        if ( scaled != fitted )
        {
#if 0
          if ( dim == AF_DIMENSION_HORZ )
          {
            if ( fitted < scaled )
              scale -= scale / 50;  /* scale *= 0.98 */
          }
          else
#endif
          if ( dim == AF_DIMENSION_VERT )
          {
            FT_Pos    max_height;
            FT_Pos    dist;
            FT_Fixed  new_scale;


            new_scale = FT_MulDiv( scale, fitted, scaled );

            /* the scaling should not change the result by more than two pixels */
            max_height = metrics->units_per_em;

            for ( nn = 0; nn < Axis->blue_count; nn++ )
            {
              max_height = FT_MAX( max_height, Axis->blues[nn].ascender );
              max_height = FT_MAX( max_height, -Axis->blues[nn].descender );
            }

            dist = FT_MulFix( max_height, new_scale - scale );

            if ( -128 < dist && dist < 128 )
            {
              FT_TRACE5(( "af_latin_metrics_scale_dim:"
                          " x height alignment (style `%s'):\n",
                          af_style_names[metrics->root.style_class->style] ));
              FT_TRACE5(( "                           "
                          " vertical scaling changed"
                          " from %.5f to %.5f (by %ld%%)\n",
                          (double)scale / 65536,
                          (double)new_scale / 65536,
                          ( fitted - scaled ) * 100 / scaled ));
              FT_TRACE5(( "\n" ));

              scale = new_scale;
            }
#ifdef FT_DEBUG_LEVEL_TRACE
            else
            {
              FT_TRACE5(( "af_latin_metrics_scale_dim:"
                          " x height alignment (style `%s'):\n",
                          af_style_names[metrics->root.style_class->style] ));
              FT_TRACE5(( "                           "
                          " excessive vertical scaling abandoned\n" ));
              FT_TRACE5(( "\n" ));
            }
#endif
          }
        }
      }
    }

    axis->scale = scale;
    axis->delta = delta;

    if ( dim == AF_DIMENSION_HORZ )
    {
      metrics->root.scaler.x_scale = scale;
      metrics->root.scaler.x_delta = delta;
    }
    else
    {
      metrics->root.scaler.y_scale = scale;
      metrics->root.scaler.y_delta = delta;
    }

    FT_TRACE5(( "%s widths (style `%s')\n",
                dim == AF_DIMENSION_HORZ ? "horizontal" : "vertical",
                af_style_names[metrics->root.style_class->style] ));

    /* scale the widths */
    for ( nn = 0; nn < axis->width_count; nn++ )
    {
      AF_Width  width = axis->widths + nn;


      width->cur = FT_MulFix( width->org, scale );
      width->fit = width->cur;

      FT_TRACE5(( "  %ld scaled to %.2f\n",
                  width->org,
                  (double)width->cur / 64 ));
    }

    FT_TRACE5(( "\n" ));

    /* an extra-light axis corresponds to a standard width that is */
    /* smaller than 5/8 pixels                                     */
    axis->extra_light =
      FT_BOOL( FT_MulFix( axis->standard_width, scale ) < 32 + 8 );

#ifdef FT_DEBUG_LEVEL_TRACE
    if ( axis->extra_light )
    {
      FT_TRACE5(( "`%s' style is extra light (at current resolution)\n",
                  af_style_names[metrics->root.style_class->style] ));
      FT_TRACE5(( "\n" ));
    }
#endif

    if ( dim == AF_DIMENSION_VERT )
    {
#ifdef FT_DEBUG_LEVEL_TRACE
      if ( axis->blue_count )
        FT_TRACE5(( "blue zones (style `%s')\n",
                    af_style_names[metrics->root.style_class->style] ));
#endif

      /* scale the blue zones */
      for ( nn = 0; nn < axis->blue_count; nn++ )
      {
        AF_LatinBlue  blue = &axis->blues[nn];
        FT_Pos        dist;


        blue->ref.cur   = FT_MulFix( blue->ref.org, scale ) + delta;
        blue->ref.fit   = blue->ref.cur;
        blue->shoot.cur = FT_MulFix( blue->shoot.org, scale ) + delta;
        blue->shoot.fit = blue->shoot.cur;
        blue->flags    &= ~AF_LATIN_BLUE_ACTIVE;

        /* a blue zone is only active if it is less than 3/4 pixels tall */
        dist = FT_MulFix( blue->ref.org - blue->shoot.org, scale );
        if ( dist <= 48 && dist >= -48 )
        {
#if 0
          FT_Pos  delta1;
#endif
          FT_Pos  delta2;


          /* use discrete values for blue zone widths */

#if 0

          /* generic, original code */
          delta1 = blue->shoot.org - blue->ref.org;
          delta2 = delta1;
          if ( delta1 < 0 )
            delta2 = -delta2;

          delta2 = FT_MulFix( delta2, scale );

          if ( delta2 < 32 )
            delta2 = 0;
          else if ( delta2 < 64 )
            delta2 = 32 + ( ( ( delta2 - 32 ) + 16 ) & ~31 );
          else
            delta2 = FT_PIX_ROUND( delta2 );

          if ( delta1 < 0 )
            delta2 = -delta2;

          blue->ref.fit   = FT_PIX_ROUND( blue->ref.cur );
          blue->shoot.fit = blue->ref.fit + delta2;

#else

          /* simplified version due to abs(dist) <= 48 */
          delta2 = dist;
          if ( dist < 0 )
            delta2 = -delta2;

          if ( delta2 < 32 )
            delta2 = 0;
          else if ( delta2 < 48 )
            delta2 = 32;
          else
            delta2 = 64;

          if ( dist < 0 )
            delta2 = -delta2;

          blue->ref.fit   = FT_PIX_ROUND( blue->ref.cur );
          blue->shoot.fit = blue->ref.fit - delta2;

#endif

          blue->flags |= AF_LATIN_BLUE_ACTIVE;
        }
      }

      /* use sub-top blue zone only if it doesn't overlap with */
      /* another (non-sup-top) blue zone; otherwise, the       */
      /* effect would be similar to a neutral blue zone, which */
      /* is not desired here                                   */
      for ( nn = 0; nn < axis->blue_count; nn++ )
      {
        AF_LatinBlue  blue = &axis->blues[nn];
        FT_UInt       i;


        if ( !( blue->flags & AF_LATIN_BLUE_SUB_TOP ) )
          continue;
        if ( !( blue->flags & AF_LATIN_BLUE_ACTIVE ) )
          continue;

        for ( i = 0; i < axis->blue_count; i++ )
        {
          AF_LatinBlue  b = &axis->blues[i];


          if ( b->flags & AF_LATIN_BLUE_SUB_TOP )
            continue;
          if ( !( b->flags & AF_LATIN_BLUE_ACTIVE ) )
            continue;

          if ( b->ref.fit <= blue->shoot.fit &&
               b->shoot.fit >= blue->ref.fit )
          {
            blue->flags &= ~AF_LATIN_BLUE_ACTIVE;
            break;
          }
        }
      }

#ifdef FT_DEBUG_LEVEL_TRACE
      for ( nn = 0; nn < axis->blue_count; nn++ )
      {
        AF_LatinBlue  blue = &axis->blues[nn];


        FT_TRACE5(( "  reference %u: %ld scaled to %.2f%s\n",
                    nn,
                    blue->ref.org,
                    (double)blue->ref.fit / 64,
                    ( blue->flags & AF_LATIN_BLUE_ACTIVE ) ? ""
                                                           : " (inactive)" ));
        FT_TRACE5(( "  overshoot %u: %ld scaled to %.2f%s\n",
                    nn,
                    blue->shoot.org,
                    (double)blue->shoot.fit / 64,
                    ( blue->flags & AF_LATIN_BLUE_ACTIVE ) ? ""
                                                           : " (inactive)" ));
      }
#endif
    }
  }


  FT_CALLBACK_DEF( void )
  af_latin_metrics_done( AF_StyleMetrics  metrics_ )
  {
    AF_LatinMetrics  metrics = (AF_LatinMetrics)metrics_;


    af_reverse_character_map_done( metrics->root.reverse_charmap,
                                   metrics->root.globals->face->memory );
  }


  /* Scale global values in both directions. */

  FT_LOCAL_DEF( void )
  af_latin_metrics_scale( AF_StyleMetrics  metrics_,   /* AF_LatinMetrics */
                          AF_Scaler        scaler )
  {
    AF_LatinMetrics  metrics = (AF_LatinMetrics)metrics_;


    metrics->root.scaler.render_mode = scaler->render_mode;
    metrics->root.scaler.face        = scaler->face;
    metrics->root.scaler.flags       = scaler->flags;

    af_latin_metrics_scale_dim( metrics, scaler, AF_DIMENSION_HORZ );
    af_latin_metrics_scale_dim( metrics, scaler, AF_DIMENSION_VERT );
  }


  /* Extract standard_width from writing system/script specific */
  /* metrics class.                                             */

  FT_CALLBACK_DEF( void )
  af_latin_get_standard_widths( AF_StyleMetrics  metrics_, /* AF_LatinMetrics */
                                FT_Pos*          stdHW,
                                FT_Pos*          stdVW )
  {
    AF_LatinMetrics  metrics = (AF_LatinMetrics)metrics_;


    if ( stdHW )
      *stdHW = metrics->axis[AF_DIMENSION_VERT].standard_width;

    if ( stdVW )
      *stdVW = metrics->axis[AF_DIMENSION_HORZ].standard_width;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****           L A T I N   G L Y P H   A N A L Y S I S             *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/


  /* Walk over all contours and compute its segments. */

  FT_LOCAL_DEF( FT_Error )
  af_latin_hints_compute_segments( AF_GlyphHints  hints,
                                   AF_Dimension   dim )
  {
    AF_LatinMetrics  metrics       = (AF_LatinMetrics)hints->metrics;
    AF_AxisHints     axis          = &hints->axis[dim];
    FT_Memory        memory        = hints->memory;
    FT_Error         error         = FT_Err_Ok;
    AF_Segment       segment       = NULL;
    AF_SegmentRec    seg0;
    AF_Point*        contour       = hints->contours;
    AF_Point*        contour_limit = contour + hints->num_contours;
    AF_Direction     major_dir, segment_dir;

    FT_Pos  flat_threshold = FLAT_THRESHOLD( metrics->units_per_em );


    FT_ZERO( &seg0 );
    seg0.score = 32000;
    seg0.flags = AF_EDGE_NORMAL;

    major_dir   = (AF_Direction)FT_ABS( axis->major_dir );
    segment_dir = major_dir;

    axis->num_segments = 0;

    /* set up (u,v) in each point */
    if ( dim == AF_DIMENSION_HORZ )
    {
      AF_Point  point = hints->points;
      AF_Point  limit = point + hints->num_points;


      for ( ; point < limit; point++ )
      {
        point->u = point->fx;
        point->v = point->fy;
      }
    }
    else
    {
      AF_Point  point = hints->points;
      AF_Point  limit = point + hints->num_points;


      for ( ; point < limit; point++ )
      {
        point->u = point->fy;
        point->v = point->fx;
      }
    }

    /* do each contour separately */
    for ( ; contour < contour_limit; contour++ )
    {
      AF_Point  point   = contour[0];
      AF_Point  last    = point->prev;
      int       on_edge = 0;

      /* we call values measured along a segment (point->v)    */
      /* `coordinates', and values orthogonal to it (point->u) */
      /* `positions'                                           */
      FT_Pos     min_pos      =  32000;
      FT_Pos     max_pos      = -32000;
      FT_Pos     min_coord    =  32000;
      FT_Pos     max_coord    = -32000;
      FT_UShort  min_flags    =  AF_FLAG_NONE;
      FT_UShort  max_flags    =  AF_FLAG_NONE;
      FT_Pos     min_on_coord =  32000;
      FT_Pos     max_on_coord = -32000;

      FT_Bool  passed;

      AF_Segment  prev_segment = NULL;

      FT_Pos     prev_min_pos      = min_pos;
      FT_Pos     prev_max_pos      = max_pos;
      FT_Pos     prev_min_coord    = min_coord;
      FT_Pos     prev_max_coord    = max_coord;
      FT_UShort  prev_min_flags    = min_flags;
      FT_UShort  prev_max_flags    = max_flags;
      FT_Pos     prev_min_on_coord = min_on_coord;
      FT_Pos     prev_max_on_coord = max_on_coord;


      if ( !( point->flags & AF_FLAG_IGNORE )    &&
           FT_ABS( last->out_dir )  == major_dir &&
           FT_ABS( point->out_dir ) == major_dir )
      {
        /* we are already on an edge, try to locate its start */
        last = point;

        for (;;)
        {
          point = point->prev;
          if ( FT_ABS( point->out_dir ) != major_dir )
          {
            point = point->next;
            break;
          }
          if ( point == last )
            break;
        }
      }

      last   = point;
      passed = 0;

      for (;;)
      {
        FT_Pos  u, v;


        if ( on_edge )
        {
          /* get minimum and maximum position */
          u = point->u;
          if ( u < min_pos )
            min_pos = u;
          if ( u > max_pos )
            max_pos = u;

          /* get minimum and maximum coordinate together with flags */
          v = point->v;
          if ( v < min_coord )
          {
            min_coord = v;
            min_flags = point->flags;
          }
          if ( v > max_coord )
          {
            max_coord = v;
            max_flags = point->flags;
          }

          /* get minimum and maximum coordinate of `on' points */
          if ( !( point->flags & AF_FLAG_CONTROL ) )
          {
            v = point->v;
            if ( v < min_on_coord )
              min_on_coord = v;
            if ( v > max_on_coord )
              max_on_coord = v;
          }

          if ( point->flags & AF_FLAG_IGNORE ||
               point->out_dir != segment_dir ||
               point == last )
          {
            /* check whether the new segment's start point is identical to */
            /* the previous segment's end point; for example, this might   */
            /* happen for spikes                                           */

            if ( point->flags & AF_FLAG_IGNORE        ||
                 !prev_segment                        ||
                 segment->first != prev_segment->last )
            {
              /* points are different: we are just leaving an edge, thus */
              /* record a new segment                                    */

              segment->last  = point;
              segment->pos   = (FT_Short)( ( min_pos + max_pos ) >> 1 );
              segment->delta = (FT_Short)( ( max_pos - min_pos ) >> 1 );

              /* a segment is round if either its first or last point */
              /* is a control point, and the length of the on points  */
              /* inbetween doesn't exceed a heuristic limit           */
              if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL      &&
                   ( max_on_coord - min_on_coord ) < flat_threshold )
                segment->flags |= AF_EDGE_ROUND;

              segment->min_coord = (FT_Short)min_coord;
              segment->max_coord = (FT_Short)max_coord;
              segment->height    = segment->max_coord - segment->min_coord;

              prev_segment      = segment;
              prev_min_pos      = min_pos;
              prev_max_pos      = max_pos;
              prev_min_coord    = min_coord;
              prev_max_coord    = max_coord;
              prev_min_flags    = min_flags;
              prev_max_flags    = max_flags;
              prev_min_on_coord = min_on_coord;
              prev_max_on_coord = max_on_coord;
            }
            else
            {
              /* points are the same: we don't create a new segment but */
              /* merge the current segment with the previous one        */

              if ( prev_segment->last->in_dir == point->in_dir )
              {
                /* we have identical directions (this can happen for       */
                /* degenerate outlines that move zig-zag along the main    */
                /* axis without changing the coordinate value of the other */
                /* axis, and where the segments have just been merged):    */
                /* unify segments                                          */

                /* update constraints */

                if ( prev_min_pos < min_pos )
                  min_pos = prev_min_pos;
                if ( prev_max_pos > max_pos )
                  max_pos = prev_max_pos;

                if ( prev_min_coord < min_coord )
                {
                  min_coord = prev_min_coord;
                  min_flags = prev_min_flags;
                }
                if ( prev_max_coord > max_coord )
                {
                  max_coord = prev_max_coord;
                  max_flags = prev_max_flags;
                }

                if ( prev_min_on_coord < min_on_coord )
                  min_on_coord = prev_min_on_coord;
                if ( prev_max_on_coord > max_on_coord )
                  max_on_coord = prev_max_on_coord;

                prev_segment->last  = point;
                prev_segment->pos   = (FT_Short)( ( min_pos +
                                                    max_pos ) >> 1 );
                prev_segment->delta = (FT_Short)( ( max_pos -
                                                    min_pos ) >> 1 );

                if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL      &&
                     ( max_on_coord - min_on_coord ) < flat_threshold )
                  prev_segment->flags |= AF_EDGE_ROUND;
                else
                  prev_segment->flags &= ~AF_EDGE_ROUND;

                prev_segment->min_coord = (FT_Short)min_coord;
                prev_segment->max_coord = (FT_Short)max_coord;
                prev_segment->height    = prev_segment->max_coord -
                                          prev_segment->min_coord;
              }
              else
              {
                /* we have different directions; use the properties of the */
                /* longer segment and discard the other one                */

                if ( FT_ABS( prev_max_coord - prev_min_coord ) >
                     FT_ABS( max_coord - min_coord ) )
                {
                  /* discard current segment */

                  if ( min_pos < prev_min_pos )
                    prev_min_pos = min_pos;
                  if ( max_pos > prev_max_pos )
                    prev_max_pos = max_pos;

                  prev_segment->last  = point;
                  prev_segment->pos   = (FT_Short)( ( prev_min_pos +
                                                      prev_max_pos ) >> 1 );
                  prev_segment->delta = (FT_Short)( ( prev_max_pos -
                                                      prev_min_pos ) >> 1 );
                }
                else
                {
                  /* discard previous segment */

                  if ( prev_min_pos < min_pos )
                    min_pos = prev_min_pos;
                  if ( prev_max_pos > max_pos )
                    max_pos = prev_max_pos;

                  segment->last  = point;
                  segment->pos   = (FT_Short)( ( min_pos + max_pos ) >> 1 );
                  segment->delta = (FT_Short)( ( max_pos - min_pos ) >> 1 );

                  if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL      &&
                       ( max_on_coord - min_on_coord ) < flat_threshold )
                    segment->flags |= AF_EDGE_ROUND;

                  segment->min_coord = (FT_Short)min_coord;
                  segment->max_coord = (FT_Short)max_coord;
                  segment->height    = segment->max_coord -
                                       segment->min_coord;

                  *prev_segment = *segment;

                  prev_min_pos      = min_pos;
                  prev_max_pos      = max_pos;
                  prev_min_coord    = min_coord;
                  prev_max_coord    = max_coord;
                  prev_min_flags    = min_flags;
                  prev_max_flags    = max_flags;
                  prev_min_on_coord = min_on_coord;
                  prev_max_on_coord = max_on_coord;
                }
              }

              axis->num_segments--;
            }

            on_edge = 0;
            segment = NULL;

            /* fall through */
          }
        }

        /* now exit if we are at the start/end point */
        if ( point == last )
        {
          if ( passed )
            break;
          passed = 1;
        }

        /* if we are not on an edge, check whether the major direction */
        /* coincides with the current point's `out' direction, or      */
        /* whether we have a single-point contour                      */
        if ( !( point->flags & AF_FLAG_IGNORE )        &&
             !on_edge                                  &&
             ( FT_ABS( point->out_dir ) == major_dir ||
               point == point->prev                  ) )
        {
          /*
           * For efficiency, we restrict the number of segments to 1000,
           * which is a heuristic value: it is very unlikely that a glyph
           * with so many segments can be hinted in a sensible way.
           * Reasons:
           *
           * - The glyph has really 1000 segments; this implies that it has
           *   at least 2000 outline points.  Assuming 'normal' fonts that
           *   have superfluous points optimized away, viewing such a glyph
           *   only makes sense at large magnifications where hinting
           *   isn't applied anyway.
           *
           * - We have a broken glyph.  Hinting doesn't make sense in this
           *   case either.
           */
          if ( axis->num_segments > 1000 )
          {
            FT_TRACE0(( "af_latin_hints_compute_segments:"
                        " more than 1000 segments in this glyph;\n" ));
            FT_TRACE0(( "                                "
                        " hinting is suppressed\n" ));
            axis->num_segments = 0;
            return FT_Err_Ok;
          }

          /* this is the start of a new segment! */
          segment_dir = (AF_Direction)point->out_dir;

          error = af_axis_hints_new_segment( axis, memory, &segment );
          if ( error )
            goto Exit;

          /* clear all segment fields */
          segment[0] = seg0;

          segment->dir   = (FT_Char)segment_dir;
          segment->first = point;
          segment->last  = point;

          /* `af_axis_hints_new_segment' reallocates memory,    */
          /* thus we have to refresh the `prev_segment' pointer */
          if ( prev_segment )
            prev_segment = segment - 1;

          min_pos   = max_pos   = point->u;
          min_coord = max_coord = point->v;
          min_flags = max_flags = point->flags;

          if ( point->flags & AF_FLAG_CONTROL )
          {
            min_on_coord =  32000;
            max_on_coord = -32000;
          }
          else
            min_on_coord = max_on_coord = point->v;

          on_edge = 1;

          if ( point == point->prev )
          {
            /* we have a one-point segment: this is a one-point */
            /* contour with `in' and `out' direction set to     */
            /* AF_DIR_NONE                                      */
            segment->pos = (FT_Short)min_pos;

            if (point->flags & AF_FLAG_CONTROL)
              segment->flags |= AF_EDGE_ROUND;

            segment->min_coord = (FT_Short)point->v;
            segment->max_coord = (FT_Short)point->v;
            segment->height = 0;

            on_edge = 0;
            segment = NULL;
          }
        }

        point = point->next;
      }

    } /* contours */


    /* now slightly increase the height of segments if this makes */
    /* sense -- this is used to better detect and ignore serifs   */
    {
      AF_Segment  segments     = axis->segments;
      AF_Segment  segments_end = FT_OFFSET( segments, axis->num_segments );


      for ( segment = segments; segment < segments_end; segment++ )
      {
        AF_Point  first   = segment->first;
        AF_Point  last    = segment->last;
        FT_Pos    first_v = first->v;
        FT_Pos    last_v  = last->v;


        if ( first_v < last_v )
        {
          AF_Point  p;


          p = first->prev;
          if ( p->v < first_v )
            segment->height = (FT_Short)( segment->height +
                                          ( ( first_v - p->v ) >> 1 ) );

          p = last->next;
          if ( p->v > last_v )
            segment->height = (FT_Short)( segment->height +
                                          ( ( p->v - last_v ) >> 1 ) );
        }
        else
        {
          AF_Point  p;


          p = first->prev;
          if ( p->v > first_v )
            segment->height = (FT_Short)( segment->height +
                                          ( ( p->v - first_v ) >> 1 ) );

          p = last->next;
          if ( p->v < last_v )
            segment->height = (FT_Short)( segment->height +
                                          ( ( last_v - p->v ) >> 1 ) );
        }
      }
    }

  Exit:
    return error;
  }


  /* Link segments to form stems and serifs.  If `width_count' and      */
  /* `widths' are non-zero, use them to fine-tune the scoring function. */

  FT_LOCAL_DEF( void )
  af_latin_hints_link_segments( AF_GlyphHints  hints,
                                FT_UInt        width_count,
                                AF_WidthRec*   widths,
                                AF_Dimension   dim )
  {
    AF_AxisHints  axis          = &hints->axis[dim];
    AF_Segment    segments      = axis->segments;
    AF_Segment    segment_limit = FT_OFFSET( segments, axis->num_segments );
    FT_Pos        len_threshold, len_score, dist_score, max_width;
    AF_Segment    seg1, seg2;


    if ( width_count )
      max_width = widths[width_count - 1].org;
    else
      max_width = 0;

    /* a heuristic value to set up a minimum value for overlapping */
    len_threshold = AF_LATIN_CONSTANT( hints->metrics, 8 );
    if ( len_threshold == 0 )
      len_threshold = 1;

    /* a heuristic value to weight lengths */
    len_score = AF_LATIN_CONSTANT( hints->metrics, 6000 );

    /* a heuristic value to weight distances (no call to    */
    /* AF_LATIN_CONSTANT needed, since we work on multiples */
    /* of the stem width)                                   */
    dist_score = 3000;

    /* now compare each segment to the others */
    for ( seg1 = segments; seg1 < segment_limit; seg1++ )
    {
      if ( seg1->dir != axis->major_dir )
        continue;

      /* search for stems having opposite directions, */
      /* with seg1 to the `left' of seg2              */
      for ( seg2 = segments; seg2 < segment_limit; seg2++ )
      {
        FT_Pos  pos1 = seg1->pos;
        FT_Pos  pos2 = seg2->pos;


        if ( seg1->dir + seg2->dir == 0 && pos2 > pos1 )
        {
          /* compute distance between the two segments */
          FT_Pos  min = seg1->min_coord;
          FT_Pos  max = seg1->max_coord;
          FT_Pos  len;


          if ( min < seg2->min_coord )
            min = seg2->min_coord;

          if ( max > seg2->max_coord )
            max = seg2->max_coord;

          /* compute maximum coordinate difference of the two segments */
          /* (that is, how much they overlap)                          */
          len = max - min;
          if ( len >= len_threshold )
          {
            /*
             * The score is the sum of two demerits indicating the
             * `badness' of a fit, measured along the segments' main axis
             * and orthogonal to it, respectively.
             *
             * - The less overlapping along the main axis, the worse it
             *   is, causing a larger demerit.
             *
             * - The nearer the orthogonal distance to a stem width, the
             *   better it is, causing a smaller demerit.  For simplicity,
             *   however, we only increase the demerit for values that
             *   exceed the largest stem width.
             */

            FT_Pos  dist = pos2 - pos1;

            FT_Pos  dist_demerit, score;


            if ( max_width )
            {
              /* distance demerits are based on multiples of `max_width'; */
              /* we scale by 1024 for getting more precision              */
              FT_Pos  delta = ( dist << 10 ) / max_width - ( 1 << 10 );


              if ( delta > 10000 )
                dist_demerit = 32000;
              else if ( delta > 0 )
                dist_demerit = delta * delta / dist_score;
              else
                dist_demerit = 0;
            }
            else
              dist_demerit = dist; /* default if no widths available */

            score = dist_demerit + len_score / len;

            /* and we search for the smallest score */
            if ( score < seg1->score )
            {
              seg1->score = score;
              seg1->link  = seg2;
            }

            if ( score < seg2->score )
            {
              seg2->score = score;
              seg2->link  = seg1;
            }
          }
        }
      }
    }

    /* now compute the `serif' segments, cf. explanations in `afhints.h' */
    for ( seg1 = segments; seg1 < segment_limit; seg1++ )
    {
      seg2 = seg1->link;

      if ( seg2 )
      {
        if ( seg2->link != seg1 )
        {
          seg1->link  = NULL;
          seg1->serif = seg2->link;
        }
      }
    }
  }


  /* Link segments to edges, using feature analysis for selection. */

  FT_LOCAL_DEF( FT_Error )
  af_latin_hints_compute_edges( AF_GlyphHints  hints,
                                AF_Dimension   dim )
  {
    AF_AxisHints  axis   = &hints->axis[dim];
    FT_Error      error  = FT_Err_Ok;
    FT_Memory     memory = hints->memory;
    AF_LatinAxis  laxis  = &((AF_LatinMetrics)hints->metrics)->axis[dim];

    AF_StyleClass   style_class  = hints->metrics->style_class;
    AF_ScriptClass  script_class = af_script_classes[style_class->script];

    FT_Bool  top_to_bottom_hinting = 0;

    AF_Segment    segments      = axis->segments;
    AF_Segment    segment_limit = FT_OFFSET( segments, axis->num_segments );
    AF_Segment    seg;

#if 0
    AF_Direction  up_dir;
#endif
    FT_Fixed      scale;
    FT_Pos        edge_distance_threshold;
    FT_Pos        segment_length_threshold;
    FT_Pos        segment_width_threshold;


    axis->num_edges = 0;

    scale = ( dim == AF_DIMENSION_HORZ ) ? hints->x_scale
                                         : hints->y_scale;

#if 0
    up_dir = ( dim == AF_DIMENSION_HORZ ) ? AF_DIR_UP
                                          : AF_DIR_RIGHT;
#endif

    if ( dim == AF_DIMENSION_VERT )
      top_to_bottom_hinting = script_class->top_to_bottom_hinting;

    /*
     * We ignore all segments that are less than 1 pixel in length
     * to avoid many problems with serif fonts.  We compute the
     * corresponding threshold in font units.
     */
    if ( dim == AF_DIMENSION_HORZ )
      segment_length_threshold = FT_DivFix( 64, hints->y_scale );
    else
      segment_length_threshold = 0;

    /*
     * Similarly, we ignore segments that have a width delta
     * larger than 0.5px (i.e., a width larger than 1px).
     */
    segment_width_threshold = FT_DivFix( 32, scale );

    /**********************************************************************
     *
     * We begin by generating a sorted table of edges for the current
     * direction.  To do so, we simply scan each segment and try to find
     * an edge in our table that corresponds to its position.
     *
     * If no edge is found, we create and insert a new edge in the
     * sorted table.  Otherwise, we simply add the segment to the edge's
     * list which gets processed in the second step to compute the
     * edge's properties.
     *
     * Note that the table of edges is sorted along the segment/edge
     * position.
     *
     */

    /* assure that edge distance threshold is at most 0.25px */
    edge_distance_threshold = FT_MulFix( laxis->edge_distance_threshold,
                                         scale );
    if ( edge_distance_threshold > 64 / 4 )
      edge_distance_threshold = 64 / 4;

    edge_distance_threshold = FT_DivFix( edge_distance_threshold,
                                         scale );

    for ( seg = segments; seg < segment_limit; seg++ )
    {
      AF_Edge  found = NULL;
      FT_UInt  ee;


      /* ignore too short segments, too wide ones, and, in this loop, */
      /* one-point segments without a direction                       */
      if ( seg->height < segment_length_threshold ||
           seg->delta > segment_width_threshold   ||
           seg->dir == AF_DIR_NONE                )
        continue;

      /* A special case for serif edges: If they are smaller than */
      /* 1.5 pixels we ignore them.                               */
      if ( seg->serif                                     &&
           2 * seg->height < 3 * segment_length_threshold )
        continue;

      /* look for an edge corresponding to the segment */
      for ( ee = 0; ee < axis->num_edges; ee++ )
      {
        AF_Edge  edge = axis->edges + ee;
        FT_Pos   dist;


        dist = seg->pos - edge->fpos;
        if ( dist < 0 )
          dist = -dist;

        if ( dist < edge_distance_threshold && edge->dir == seg->dir )
        {
          found = edge;
          break;
        }
      }

      if ( !found )
      {
        AF_Edge  edge;


        /* insert a new edge in the list and */
        /* sort according to the position    */
        error = af_axis_hints_new_edge( axis, seg->pos,
                                        (AF_Direction)seg->dir,
                                        top_to_bottom_hinting,
                                        memory, &edge );
        if ( error )
          goto Exit;

        /* add the segment to the new edge's list */
        FT_ZERO( edge );

        edge->first    = seg;
        edge->last     = seg;
        edge->dir      = seg->dir;
        edge->fpos     = seg->pos;
        edge->opos     = FT_MulFix( seg->pos, scale );
        edge->pos      = edge->opos;
        seg->edge_next = seg;
      }
      else
      {
        /* if an edge was found, simply add the segment to the edge's */
        /* list                                                       */
        seg->edge_next         = found->first;
        found->last->edge_next = seg;
        found->last            = seg;
      }
    }

    /* we loop again over all segments to catch one-point segments   */
    /* without a direction: if possible, link them to existing edges */
    for ( seg = segments; seg < segment_limit; seg++ )
    {
      AF_Edge  found = NULL;
      FT_UInt  ee;


      if ( seg->dir != AF_DIR_NONE )
        continue;

      /* look for an edge corresponding to the segment */
      for ( ee = 0; ee < axis->num_edges; ee++ )
      {
        AF_Edge  edge = axis->edges + ee;
        FT_Pos   dist;


        dist = seg->pos - edge->fpos;
        if ( dist < 0 )
          dist = -dist;

        if ( dist < edge_distance_threshold )
        {
          found = edge;
          break;
        }
      }

      /* one-point segments without a match are ignored */
      if ( found )
      {
        seg->edge_next         = found->first;
        found->last->edge_next = seg;
        found->last            = seg;
      }
    }


    /*******************************************************************
     *
     * Good, we now compute each edge's properties according to the
     * segments found on its position.  Basically, these are
     *
     * - the edge's main direction
     * - stem edge, serif edge or both (which defaults to stem then)
     * - rounded edge, straight or both (which defaults to straight)
     * - link for edge
     *
     */

    /* first of all, set the `edge' field in each segment -- this is */
    /* required in order to compute edge links                       */

    /*
     * Note that removing this loop and setting the `edge' field of each
     * segment directly in the code above slows down execution speed for
     * some reasons on platforms like the Sun.
     */
    {
      AF_Edge  edges      = axis->edges;
      AF_Edge  edge_limit = FT_OFFSET( edges, axis->num_edges );
      AF_Edge  edge;


      for ( edge = edges; edge < edge_limit; edge++ )
      {
        seg = edge->first;
        if ( seg )
          do
          {
            seg->edge = edge;
            seg       = seg->edge_next;

          } while ( seg != edge->first );
      }

      /* now compute each edge properties */
      for ( edge = edges; edge < edge_limit; edge++ )
      {
        FT_Int  is_round    = 0;  /* does it contain round segments?    */
        FT_Int  is_straight = 0;  /* does it contain straight segments? */
#if 0
        FT_Pos  ups         = 0;  /* number of upwards segments         */
        FT_Pos  downs       = 0;  /* number of downwards segments       */
#endif


        seg = edge->first;

        do
        {
          FT_Bool  is_serif;


          /* check for roundness of segment */
          if ( seg->flags & AF_EDGE_ROUND )
            is_round++;
          else
            is_straight++;

#if 0
          /* check for segment direction */
          if ( seg->dir == up_dir )
            ups   += seg->max_coord - seg->min_coord;
          else
            downs += seg->max_coord - seg->min_coord;
#endif

          /* check for links -- if seg->serif is set, then seg->link must */
          /* be ignored                                                   */
          is_serif = FT_BOOL( seg->serif               &&
                              seg->serif->edge         &&
                              seg->serif->edge != edge );

          if ( ( seg->link && seg->link->edge ) || is_serif )
          {
            AF_Edge     edge2;
            AF_Segment  seg2;


            edge2 = edge->link;
            seg2  = seg->link;

            if ( is_serif )
            {
              seg2  = seg->serif;
              edge2 = edge->serif;
            }

            if ( edge2 )
            {
              FT_Pos  edge_delta;
              FT_Pos  seg_delta;


              edge_delta = edge->fpos - edge2->fpos;
              if ( edge_delta < 0 )
                edge_delta = -edge_delta;

              seg_delta = seg->pos - seg2->pos;
              if ( seg_delta < 0 )
                seg_delta = -seg_delta;

              if ( seg_delta < edge_delta )
                edge2 = seg2->edge;
            }
            else
              edge2 = seg2->edge;

            if ( is_serif )
            {
              edge->serif   = edge2;
              edge2->flags |= AF_EDGE_SERIF;
            }
            else
              edge->link  = edge2;
          }

          seg = seg->edge_next;

        } while ( seg != edge->first );

        /* set the round/straight flags */
        edge->flags = AF_EDGE_NORMAL;

        if ( is_round > 0 && is_round >= is_straight )
          edge->flags |= AF_EDGE_ROUND;

#if 0
        /* set the edge's main direction */
        edge->dir = AF_DIR_NONE;

        if ( ups > downs )
          edge->dir = (FT_Char)up_dir;

        else if ( ups < downs )
          edge->dir = (FT_Char)-up_dir;

        else if ( ups == downs )
          edge->dir = 0;  /* both up and down! */
#endif

        /* get rid of serifs if link is set                 */
        /* XXX: This gets rid of many unpleasant artefacts! */
        /*      Example: the `c' in cour.pfa at size 13     */

        if ( edge->serif && edge->link )
          edge->serif = NULL;
      }
    }

  Exit:
    return error;
  }


  /* Detect segments and edges for given dimension. */

  FT_LOCAL_DEF( FT_Error )
  af_latin_hints_detect_features( AF_GlyphHints  hints,
                                  FT_UInt        width_count,
                                  AF_WidthRec*   widths,
                                  AF_Dimension   dim )
  {
    FT_Error  error;


    error = af_latin_hints_compute_segments( hints, dim );
    if ( !error )
    {
      af_latin_hints_link_segments( hints, width_count, widths, dim );

      error = af_latin_hints_compute_edges( hints, dim );
    }

    return error;
  }


  /* Compute all edges which lie within blue zones. */

  static void
  af_latin_hints_compute_blue_edges( AF_GlyphHints    hints,
                                     AF_LatinMetrics  metrics )
  {
    AF_AxisHints  axis       = &hints->axis[AF_DIMENSION_VERT];
    AF_Edge       edge       = axis->edges;
    AF_Edge       edge_limit = FT_OFFSET( edge, axis->num_edges );
    AF_LatinAxis  latin      = &metrics->axis[AF_DIMENSION_VERT];
    FT_Fixed      scale      = latin->scale;


    /* compute which blue zones are active, i.e. have their scaled */
    /* size < 3/4 pixels                                           */

    /* for each horizontal edge search the blue zone which is closest */
    for ( ; edge < edge_limit; edge++ )
    {
      FT_UInt   bb;
      AF_Width  best_blue            = NULL;
      FT_Bool   best_blue_is_neutral = 0;
      FT_Pos    best_dist;                 /* initial threshold */


      if ( edge->flags & AF_EDGE_NO_BLUE )
        continue;

      /* compute the initial threshold as a fraction of the EM size */
      /* (the value 40 is heuristic)                                */
      best_dist = FT_MulFix( metrics->units_per_em / 40, scale );

      /* assure a minimum distance of 0.5px */
      if ( best_dist > 64 / 2 )
        best_dist = 64 / 2;

      for ( bb = 0; bb < latin->blue_count; bb++ )
      {
        AF_LatinBlue  blue = latin->blues + bb;
        FT_Bool       is_top_blue, is_neutral_blue, is_major_dir;


        /* skip inactive blue zones (i.e., those that are too large) */
        if ( !( blue->flags & AF_LATIN_BLUE_ACTIVE ) )
          continue;

        /* if it is a top zone, check for right edges (against the major */
        /* direction); if it is a bottom zone, check for left edges (in  */
        /* the major direction) -- this assumes the TrueType convention  */
        /* for the orientation of contours                               */
        is_top_blue =
          (FT_Byte)( ( blue->flags & ( AF_LATIN_BLUE_TOP     |
                                       AF_LATIN_BLUE_SUB_TOP ) ) != 0 );
        is_neutral_blue =
          (FT_Byte)( ( blue->flags & AF_LATIN_BLUE_NEUTRAL ) != 0);
        is_major_dir =
          FT_BOOL( edge->dir == axis->major_dir );

        /* neutral blue zones are handled for both directions */
        if ( is_top_blue ^ is_major_dir || is_neutral_blue )
        {
          FT_Pos  dist;


          /* first of all, compare it to the reference position */
          dist = edge->fpos - blue->ref.org;
          if ( dist < 0 )
            dist = -dist;

          dist = FT_MulFix( dist, scale );
          if ( dist < best_dist )
          {
            best_dist            = dist;
            best_blue            = &blue->ref;
            best_blue_is_neutral = is_neutral_blue;
          }

          /* now compare it to the overshoot position and check whether */
          /* the edge is rounded, and whether the edge is over the      */
          /* reference position of a top zone, or under the reference   */
          /* position of a bottom zone (provided we don't have a        */
          /* neutral blue zone)                                         */
          if ( edge->flags & AF_EDGE_ROUND &&
               dist != 0                   &&
               !is_neutral_blue            )
          {
            FT_Bool  is_under_ref = FT_BOOL( edge->fpos < blue->ref.org );


            if ( is_top_blue ^ is_under_ref )
            {
              dist = edge->fpos - blue->shoot.org;
              if ( dist < 0 )
                dist = -dist;

              dist = FT_MulFix( dist, scale );
              if ( dist < best_dist )
              {
                best_dist            = dist;
                best_blue            = &blue->shoot;
                best_blue_is_neutral = is_neutral_blue;
              }
            }
          }
        }
      }

      if ( best_blue )
      {
        edge->blue_edge = best_blue;
        if ( best_blue_is_neutral )
          edge->flags |= AF_EDGE_NEUTRAL;
      }
    }
  }


  /* Initialize hinting engine. */

  static FT_Error
  af_latin_hints_init( AF_GlyphHints    hints,
                       AF_StyleMetrics  metrics_ )   /* AF_LatinMetrics */
  {
    AF_LatinMetrics  metrics = (AF_LatinMetrics)metrics_;

    FT_Render_Mode  mode;
    FT_UInt32       scaler_flags, other_flags;
    FT_Face         face = metrics->root.scaler.face;


    af_glyph_hints_rescale( hints, (AF_StyleMetrics)metrics );

    /*
     * correct x_scale and y_scale if needed, since they may have
     * been modified by `af_latin_metrics_scale_dim' above
     */
    hints->x_scale = metrics->axis[AF_DIMENSION_HORZ].scale;
    hints->x_delta = metrics->axis[AF_DIMENSION_HORZ].delta;
    hints->y_scale = metrics->axis[AF_DIMENSION_VERT].scale;
    hints->y_delta = metrics->axis[AF_DIMENSION_VERT].delta;

    /* compute flags depending on render mode, etc. */
    mode = metrics->root.scaler.render_mode;

    scaler_flags = hints->scaler_flags;
    other_flags  = 0;

    /*
     * We snap the width of vertical stems for the monochrome and
     * horizontal LCD rendering targets only.
     */
    if ( mode == FT_RENDER_MODE_MONO || mode == FT_RENDER_MODE_LCD )
      other_flags |= AF_LATIN_HINTS_HORZ_SNAP;

    /*
     * We snap the width of horizontal stems for the monochrome and
     * vertical LCD rendering targets only.
     */
    if ( mode == FT_RENDER_MODE_MONO || mode == FT_RENDER_MODE_LCD_V )
      other_flags |= AF_LATIN_HINTS_VERT_SNAP;

    /*
     * We adjust stems to full pixels unless in `light' or `lcd' mode.
     */
    if ( mode != FT_RENDER_MODE_LIGHT && mode != FT_RENDER_MODE_LCD )
      other_flags |= AF_LATIN_HINTS_STEM_ADJUST;

    if ( mode == FT_RENDER_MODE_MONO )
      other_flags |= AF_LATIN_HINTS_MONO;

    /*
     * In `light' or `lcd' mode we disable horizontal hinting completely.
     * We also do it if the face is italic.
     *
     * However, if warping is enabled (which only works in `light' hinting
     * mode), advance widths get adjusted, too.
     */
    if ( mode == FT_RENDER_MODE_LIGHT || mode == FT_RENDER_MODE_LCD ||
         ( face->style_flags & FT_STYLE_FLAG_ITALIC ) != 0          )
      scaler_flags |= AF_SCALER_FLAG_NO_HORIZONTAL;

    hints->scaler_flags = scaler_flags;
    hints->other_flags  = other_flags;

    return FT_Err_Ok;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****        L A T I N   G L Y P H   G R I D - F I T T I N G        *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  /* Snap a given width in scaled coordinates to one of the */
  /* current standard widths.                               */

  static FT_Pos
  af_latin_snap_width( AF_Width  widths,
                       FT_UInt   count,
                       FT_Pos    width )
  {
    FT_UInt  n;
    FT_Pos   best      = 64 + 32 + 2;
    FT_Pos   reference = width;
    FT_Pos   scaled;


    for ( n = 0; n < count; n++ )
    {
      FT_Pos  w;
      FT_Pos  dist;


      w = widths[n].cur;
      dist = width - w;
      if ( dist < 0 )
        dist = -dist;
      if ( dist < best )
      {
        best      = dist;
        reference = w;
      }
    }

    scaled = FT_PIX_ROUND( reference );

    if ( width >= reference )
    {
      if ( width < scaled + 48 )
        width = reference;
    }
    else
    {
      if ( width > scaled - 48 )
        width = reference;
    }

    return width;
  }


#undef  FT_COMPONENT
#define FT_COMPONENT  afadjust


  static void
  af_move_contour_vertically( AF_Point  contour,
                              FT_Int    movement )
  {
    AF_Point  point       = contour;
    AF_Point  first_point = point;


    if ( point )
    {
      do
      {
        point->y += movement;
        point     = point->next;

      } while ( point != first_point );
    }
  }


  /* Move all contours higher than `limit` by `delta`. */
  static void
  af_move_contours_up( AF_GlyphHints  hints,
                       FT_Pos         limit,
                       FT_Pos         delta )
  {
    FT_Int  contour;


    for ( contour = 0; contour < hints->num_contours; contour++ )
    {
      FT_Pos  min_y = hints->contour_y_minima[contour];
      FT_Pos  max_y = hints->contour_y_maxima[contour];


      if ( min_y < max_y &&
           min_y > limit )
        af_move_contour_vertically( hints->contours[contour],
                                    delta );
    }
  }


  static void
  af_move_contours_down( AF_GlyphHints  hints,
                         FT_Pos         limit,
                         FT_Pos         delta )
  {
    FT_Int  contour;


    for ( contour = 0; contour < hints->num_contours; contour++ )
    {
      FT_Pos  min_y = hints->contour_y_minima[contour];
      FT_Pos  max_y = hints->contour_y_maxima[contour];


      if ( min_y < max_y &&
           max_y < limit )
        af_move_contour_vertically( hints->contours[contour],
                                    -delta );
    }
  }


  /* Compute vertical extrema of all contours and store them in the */
  /* `contour_y_minima` and `contour_y_maxima` arrays of `hints`.   */
  static void
  af_compute_vertical_extrema( AF_GlyphHints  hints )
  {
    FT_Int  contour;


    for ( contour = 0; contour < hints->num_contours; contour++ )
    {
      FT_Pos  min_y = FT_LONG_MAX;
      FT_Pos  max_y = FT_LONG_MIN;

      AF_Point  first_point = hints->contours[contour];
      AF_Point  point       = first_point;


      if ( !first_point || first_point->next->next == first_point )
        goto End_loop;

      do
      {
        if ( point->y < min_y )
          min_y = point->y;
        if ( point->y > max_y )
          max_y = point->y;

        point = point->next;

      } while ( point != first_point );

    End_loop:
      hints->contour_y_minima[contour] = min_y;
      hints->contour_y_maxima[contour] = max_y;
    }
  }


  static FT_Int
  af_find_highest_contour( AF_GlyphHints  hints )
  {
    FT_Int  highest_contour = 0;
    FT_Pos  highest_min_y   = FT_LONG_MAX;
    FT_Pos  highest_max_y   = FT_LONG_MIN;

    FT_Int  contour;


    /* At this point we have one 'lower' (usually the base glyph)   */
    /* and one 'upper' object (usually the diacritic glyph).  If    */
    /* there are more contours, they must be enclosed within either */
    /* 'lower' or 'upper'.  To find this enclosing 'upper' contour  */
    /* it is thus sufficient to search for the contour with the     */
    /* highest y maximum value.                                     */
    for ( contour = 0; contour < hints->num_contours; contour++ )
    {
      FT_Pos  current_min_y = hints->contour_y_minima[contour];
      FT_Pos  current_max_y = hints->contour_y_maxima[contour];


      /* If we have two contours with the same maximum value, take */
      /* the one that has a smaller height.                        */
      if ( current_max_y > highest_max_y      ||
           ( current_max_y == highest_max_y &&
             current_min_y > highest_min_y  ) )
      {
        highest_min_y   = current_min_y;
        highest_max_y   = current_max_y;
        highest_contour = contour;
      }
    }

    return highest_contour;
  }


  static FT_Int
  af_find_second_highest_contour( AF_GlyphHints  hints )
  {
    FT_Int  highest_contour;
    FT_Pos  highest_min_y;

    FT_Int  second_highest_contour = 0;
    FT_Pos  second_highest_max_y   = FT_LONG_MIN;

    FT_Int  contour;


    if ( hints->num_contours < 3 )
      return 0;

    highest_contour = af_find_highest_contour( hints );
    highest_min_y   = hints->contour_y_minima[highest_contour];

    /* Search the contour with the largest vertical maximum that has a */
    /* vertical minimum lower than the vertical minimum of the topmost */
    /* contour.                                                        */
    for ( contour = 0; contour < hints->num_contours; contour++ )
    {
      FT_Pos  current_min_y;
      FT_Pos  current_max_y;


      if ( contour == highest_contour )
        continue;

      current_min_y = hints->contour_y_minima[contour];
      current_max_y = hints->contour_y_maxima[contour];

      if ( current_max_y > second_highest_max_y &&
           current_min_y < highest_min_y        )
      {
        second_highest_max_y   = current_max_y;
        second_highest_contour = contour;
      }
    }

    return second_highest_contour;
  }


  static FT_Int
  af_find_lowest_contour( AF_GlyphHints  hints )
  {
    FT_Int  lowest_contour = 0;
    FT_Pos  lowest_min_y   = FT_LONG_MAX;
    FT_Pos  lowest_max_y   = FT_LONG_MIN;

    FT_Int  contour;


    for ( contour = 0; contour < hints->num_contours; contour++ )
    {
      FT_Pos  current_min_y = hints->contour_y_minima[contour];
      FT_Pos  current_max_y = hints->contour_y_maxima[contour];


      if ( current_min_y < lowest_min_y      ||
           ( current_min_y == lowest_min_y &&
             current_max_y < lowest_max_y  ) )
      {
        lowest_min_y   = current_min_y;
        lowest_max_y   = current_max_y;
        lowest_contour = contour;
      }
    }

    return lowest_contour;
  }


  static FT_Int
  af_find_second_lowest_contour( AF_GlyphHints  hints )
  {
    FT_Int  lowest_contour;
    FT_Pos  lowest_max_y;

    FT_Int  second_lowest_contour = 0;
    FT_Pos  second_lowest_min_y   = FT_LONG_MAX;

    FT_Int  contour;


    if ( hints->num_contours < 3 )
      return 0;

    lowest_contour = af_find_lowest_contour( hints );
    lowest_max_y   = hints->contour_y_maxima[lowest_contour];

    for ( contour = 0; contour < hints->num_contours; contour++ )
    {
      FT_Pos  current_min_y;
      FT_Pos  current_max_y;


      if ( contour == lowest_contour )
        continue;

      current_min_y = hints->contour_y_minima[contour];
      current_max_y = hints->contour_y_maxima[contour];

      if ( current_min_y < second_lowest_min_y &&
           current_max_y > lowest_max_y        )
      {
        second_lowest_min_y   = current_min_y;
        second_lowest_contour = contour;
      }
    }

    return second_lowest_contour;
  }


  /* While aligning edges to blue zones, make the auto-hinter */
  /* ignore the ones that are higher than `pos`.              */
  static void
  af_prevent_top_blue_alignment( AF_GlyphHints  hints,
                                 FT_Pos         pos )
  {
    AF_AxisHints  axis = &hints->axis[AF_DIMENSION_VERT];

    AF_Edge  edges      = axis->edges;
    AF_Edge  edge_limit = FT_OFFSET( edges, axis->num_edges );
    AF_Edge  edge;


    for ( edge = edges; edge < edge_limit; edge++ )
      if ( edge->pos > pos )
        edge->flags |= AF_EDGE_NO_BLUE;
  }


  static void
  af_prevent_bottom_blue_alignment( AF_GlyphHints  hints,
                                    FT_Pos         pos )
  {
    AF_AxisHints  axis = &hints->axis[AF_DIMENSION_VERT];

    AF_Edge  edges      = axis->edges;
    AF_Edge  edge_limit = FT_OFFSET( edges, axis->num_edges );
    AF_Edge  edge;


    for ( edge = edges; edge < edge_limit; edge++ )
      if ( edge->pos < pos )
        edge->flags |= AF_EDGE_NO_BLUE;
  }


  static void
  af_latin_get_base_glyph_blues( AF_GlyphHints  hints,
                                 FT_Bool        is_capital,
                                 AF_LatinBlue*  top,
                                 AF_LatinBlue*  bottom )
  {
    AF_LatinMetrics  metrics = (AF_LatinMetrics)hints->metrics;
    AF_LatinAxis     axis    = &metrics->axis[AF_DIMENSION_VERT];

    FT_UInt  top_flag;
    FT_UInt  bottom_flag;

    FT_UInt  i;


    top_flag  = is_capital ? AF_LATIN_BLUE_TOP
                           : AF_LATIN_BLUE_ADJUSTMENT;
    top_flag |= AF_LATIN_BLUE_ACTIVE;

    for ( i = 0; i < axis->blue_count; i++ )
      if ( ( axis->blues[i].flags & top_flag ) == top_flag )
        break;
    if ( i < axis->blue_count )
      *top = &axis->blues[i];

    bottom_flag  = is_capital ? AF_LATIN_BLUE_BOTTOM
                              : AF_LATIN_BLUE_BOTTOM_SMALL;
    bottom_flag |= AF_LATIN_BLUE_ACTIVE;

    for ( i = 0; i < axis->blue_count; i++ )
      if ( ( axis->blues[i].flags & bottom_flag ) == bottom_flag )
        break;
    if ( i < axis->blue_count )
      *bottom = &axis->blues[i];
  }


  /* Make the auto-hinter ignore top blue zones while aligning edges. */
  /* This affects everything that is higher than a vertical position  */
  /* based on the lowercase or uppercase top and bottom blue zones    */
  /* (depending on `is_capital`).                                     */
  static void
  af_latin_ignore_top( AF_GlyphHints  hints,
                       AF_LatinBlue   top_blue,
                       AF_LatinBlue   bottom_blue )
  {
    FT_Pos  base_glyph_height;
    FT_Pos  limit;


    /* Ignore blue zones that are higher than a heuristic threshold     */
    /* (value 7 corresponds to approx. 14%, which should be sufficient  */
    /* to exceed the height of uppercase serifs.  We also add a quarter */
    /* of a pixel as a safety measure.                                  */
    base_glyph_height = top_blue->shoot.cur - bottom_blue->shoot.cur;
    limit             = top_blue->shoot.cur + base_glyph_height / 7 + 16;

    af_prevent_top_blue_alignment( hints, limit );
  }


  static void
  af_latin_ignore_bottom( AF_GlyphHints  hints,
                          AF_LatinBlue   top_blue,
                          AF_LatinBlue   bottom_blue )
  {
    FT_Pos  base_glyph_height;
    FT_Pos  limit;


    base_glyph_height = top_blue->shoot.cur - bottom_blue->shoot.cur;
    limit             = bottom_blue->shoot.cur - base_glyph_height / 7 - 16;

    af_prevent_bottom_blue_alignment( hints, limit );
  }


  static void
  af_touch_contour( AF_GlyphHints  hints,
                    FT_Int         contour )
  {
    AF_Point  first_point = hints->contours[contour];
    AF_Point  p           = first_point;


    do
    {
      p = p->next;

      p->flags |= AF_FLAG_IGNORE;
      if ( !( p->flags & AF_FLAG_CONTROL ) )
        p->flags |= AF_FLAG_TOUCH_Y;

    } while ( p != first_point );
  }


  static void
  af_touch_top_contours( AF_GlyphHints  hints,
                         FT_Int         limit_contour )
  {
    FT_Pos  limit = hints->contour_y_minima[limit_contour];

    FT_Int  contour;


    for ( contour = 0; contour < hints->num_contours; contour++ )
    {
      FT_Pos  min_y = hints->contour_y_minima[contour];
      FT_Pos  max_y = hints->contour_y_maxima[contour];


      if ( min_y < max_y  &&
           min_y >= limit )
        af_touch_contour( hints, contour );
    }
  }


  static void
  af_touch_bottom_contours( AF_GlyphHints  hints,
                            FT_Int         limit_contour )
  {
    FT_Pos  limit = hints->contour_y_minima[limit_contour];

    FT_Int  contour;


    for ( contour = 0; contour < hints->num_contours; contour++ )
    {
      FT_Pos  min_y = hints->contour_y_minima[contour];
      FT_Pos  max_y = hints->contour_y_maxima[contour];


      if ( min_y < max_y  &&
           max_y <= limit )
        af_touch_contour( hints, contour );
    }
  }


  /* Stretch tilde vertically, if necessary, and return the height */
  /* difference between the original and the stretched outline.    */
  static FT_Pos
  af_latin_stretch_top_tilde( AF_GlyphHints  hints,
                              FT_Int         tilde_contour )
  {
    AF_Point  p           = hints->contours[tilde_contour];
    AF_Point  first_point = p;

    FT_Pos  min_y = hints->contour_y_minima[tilde_contour];
    FT_Pos  max_y = hints->contour_y_maxima[tilde_contour];

    FT_Pos   min_measurement   = FT_LONG_MAX;
    FT_Bool  measurement_taken = FALSE;

    FT_Pos  height;
    FT_Pos  extremum_threshold;
    FT_Pos  target_height;


    if ( min_y == max_y )
      return 0;

    FT_TRACE4(( "af_latin_stretch_top_tilde: min y: %ld, max y: %ld\n",
                min_y, max_y ));

    height             = SUB_LONG( max_y, min_y );
    extremum_threshold = height / 8;    /* Value 8 is heuristic. */

    /* Find points that are local vertical round extrema, and which   */
    /* do not coincide with the vertical extreme values (i.e., we     */
    /* search for the 'other' wiggles in the tilde), then measure the */
    /* distance to the vertical extreme values.  Try to find the one  */
    /* with the smallest distance.                                    */
    /*                                                                */
    /* The algorithm only works for tilde shapes that don't deviate   */
    /* from the standard shape too much.  In particular, the wiggles  */
    /* must be round extrema.                                         */
    do
    {
      p = p->next;

      if ( !( p->flags & AF_FLAG_CONTROL )          &&
           p->prev->y == p->y && p->next->y == p->y &&
           p->y != min_y && p->y != max_y           &&
           p->prev->flags & AF_FLAG_CONTROL         &&
           p->next->flags & AF_FLAG_CONTROL         )
      {
        /* This point could be a candidate.  Find the next and previous */
        /* on-curve points, and make sure they are both either above or */
        /* below the point, then make the measurement.                  */
        AF_Point  prev_on = p->prev;
        AF_Point  next_on = p->next;

        FT_Pos  measurement;


        while ( prev_on->flags & AF_FLAG_CONTROL )
          prev_on = prev_on->prev;
        while ( next_on->flags & AF_FLAG_CONTROL )
          next_on = next_on->next;

        if ( next_on->y > p->y && prev_on->y > p->y )
          measurement = p->y - min_y;
        else if ( next_on->y < p->y && prev_on->y < p->y )
          measurement = max_y - p->y;
        else
          continue;

        /* Ignore hits that are too near to a vertical extremum. */
        if ( measurement < extremum_threshold )
          continue;

        if ( !measurement_taken || measurement < min_measurement )
        {
          measurement_taken = TRUE;
          min_measurement   = measurement;
        }
      }

    } while ( p != first_point );

    if ( !measurement_taken )
      min_measurement = 0;

    FT_TRACE4(( "af_latin_stretch_top_tilde: min measurement %ld\n",
                min_measurement ));

    /* To preserve the stretched shape we prevent that the tilde */
    /* gets auto-hinted; we do this for all contours equal or    */
    /* above the vertical minimum of `tilde_contour`.            */
    af_touch_top_contours( hints, tilde_contour );

    /* XXX This is an important element of the algorithm; */
    /*     we need a description.                         */
    target_height = min_measurement + 64;
    if ( height >= target_height )
      return 0;

    /* Do the scaling. */
    p = first_point;
    do
    {
      p     = p->next;
      /* We adjust the height of the diacritic only, which means    */
      /* we are never dealing with large numbers and can thus avoid */
      /* `FT_MulFix`.                                               */
      p->y  = ( ( p->y - min_y ) * target_height / height ) + min_y;

    } while ( p != first_point );

    return target_height - height;
  }


  static FT_Pos
  af_latin_stretch_bottom_tilde( AF_GlyphHints  hints,
                                 FT_Int         tilde_contour )
  {
    AF_Point  p           = hints->contours[tilde_contour];
    AF_Point  first_point = p;

    FT_Pos  min_y = hints->contour_y_minima[tilde_contour];
    FT_Pos  max_y = hints->contour_y_maxima[tilde_contour];

    FT_Pos   min_measurement   = FT_LONG_MAX;
    FT_Bool  measurement_taken = FALSE;

    FT_Pos  height;
    FT_Pos  extremum_threshold;
    FT_Pos  target_height;


    if ( min_y == max_y )
      return 0;

    FT_TRACE4(( "af_latin_stretch_bottom_tilde: min y: %ld, max y: %ld\n",
                min_y, max_y ));

    height             = SUB_LONG( max_y, min_y );
    extremum_threshold = height / 8;

    do
    {
      p = p->next;

      if ( !( p->flags & AF_FLAG_CONTROL )          &&
           p->prev->y == p->y && p->next->y == p->y &&
           p->y != min_y && p->y != max_y           &&
           p->prev->flags & AF_FLAG_CONTROL         &&
           p->next->flags & AF_FLAG_CONTROL         )
      {
        AF_Point  prev_on = p->prev;
        AF_Point  next_on = p->next;

        FT_Pos  measurement;


        while ( prev_on->flags & AF_FLAG_CONTROL )
          prev_on = prev_on->prev;
        while ( next_on->flags & AF_FLAG_CONTROL )
          next_on = next_on->next;

        if ( next_on->y > p->y && prev_on->y > p->y )
          measurement = p->y - min_y;
        else if ( next_on->y < p->y && prev_on->y < p->y )
          measurement = max_y - p->y;
        else
          continue;

        if ( measurement < extremum_threshold )
          continue;

        if ( !measurement_taken || measurement < min_measurement )
        {
          measurement_taken = TRUE;
          min_measurement   = measurement;
        }
      }

    } while ( p != first_point );

    if ( !measurement_taken )
      min_measurement = 0;

    FT_TRACE4(( "af_latin_stretch_bottom_tilde: min measurement %ld\n",
                min_measurement ));

    af_touch_bottom_contours( hints, tilde_contour );

    target_height = min_measurement + 64;
    if ( height >= target_height )
      return 0;

    p = first_point;
    do
    {
      p     = p->next;
      p->y  = ( ( p->y - max_y ) * target_height / height ) + max_y;

    } while ( p != first_point );

    return target_height - height;
  }


  /*
    As part of `af_latin_stretch_top_tilde`, normally all points in the
    tilde are marked as touched, so the existing grid fitting will leave the
    tilde misaligned with the grid.

    This function moves the tilde contour down to be grid-fitted.  We assume
    that if moving the tilde down would cause it to touch or overlap another
    countour, the vertical adjustment step will fix it.

    Because the vertical adjustment step comes after all other grid-fitting
    steps, the top edge of the contour under the tilde is usually aligned
    with a horizontal grid line.  The vertical gap enforced by the vertical
    adjustment is exactly one pixel, so if the top edge of the contour below
    the tilde is on a grid line, the resulting tilde contour will also be
    grid-aligned.

    But in cases where the gap is already big enough so that the vertical
    adjustment does nothing, this function ensures that even without the
    intervention of the vertical adjustment step, the tilde will be
    grid-aligned.

    Return the vertical alignment amount.
  */
  static FT_Pos
  af_latin_align_top_tilde( AF_GlyphHints  hints,
                            FT_Int         tilde_contour )
  {
    AF_Point  p           = hints->contours[tilde_contour];
    AF_Point  first_point = p;

    FT_Pos  min_y = p->y;
    FT_Pos  max_y = p->y;

    FT_Pos  min_y_rounded;
    FT_Pos  delta;
    FT_Pos  height;


    /* Find vertical extrema of the (now stretched) tilde contour. */
    do
    {
      p = p->next;
      if ( p->y < min_y )
        min_y = p->y;
      if ( p->y > max_y )
        max_y = p->y;

    } while ( p != first_point );

    /* Align bottom of the tilde to the grid. */
    min_y_rounded = FT_PIX_ROUND( min_y );
    delta         = min_y_rounded - min_y;
    height        = max_y - min_y;

    /* If the tilde is less than 3 pixels tall, snap the center of it */
    /* to the grid instead of the bottom to improve readability.      */
    if ( height < 64 * 3 )
      delta += ( FT_PIX_ROUND( height ) - height ) / 2;

    af_move_contour_vertically( first_point, delta );

    return delta;
  }


  static FT_Pos
  af_latin_align_bottom_tilde( AF_GlyphHints  hints,
                               FT_Int         tilde_contour )
  {
    AF_Point  p           = hints->contours[tilde_contour];
    AF_Point  first_point = p;

    FT_Pos  min_y = p->y;
    FT_Pos  max_y = p->y;

    FT_Pos  max_y_rounded;
    FT_Pos  delta;
    FT_Pos  height;


    do
    {
      p = p->next;
      if ( p->y < min_y )
        min_y = p->y;
      if ( p->y > max_y )
        max_y = p->y;

    } while ( p != first_point );

    max_y_rounded = FT_PIX_ROUND( max_y );
    delta         = max_y_rounded - max_y;
    height        = max_y - min_y;

    if ( height < 64 * 3 )
      delta -= ( FT_PIX_ROUND( height ) - height ) / 2;

    af_move_contour_vertically( first_point, delta );

    return delta;
  }


  /* Return 1 if the given contour overlaps horizontally with the bounding */
  /* box of all other contours combined.  This is a helper for function    */
  /* `af_glyph_hints_apply_vertical_separation_adjustments`.               */
  static FT_Bool
  af_check_contour_horizontal_overlap( AF_GlyphHints  hints,
                                       FT_Int         contour_index )
  {
    FT_Pos  contour_max_x = FT_LONG_MIN;
    FT_Pos  contour_min_x = FT_LONG_MAX;
    FT_Pos  others_max_x  = FT_LONG_MIN;
    FT_Pos  others_min_x  = FT_LONG_MAX;

    FT_Int  contour;

    FT_Bool  horizontal_overlap;


    for ( contour = 0; contour < hints->num_contours; contour++ )
    {
      AF_Point  first_point = hints->contours[contour];
      AF_Point  p           = first_point;


      /* Ignore dimensionless contours (i.e., contours with only one or */
      /* two points).                                                   */
      if ( first_point->next->next == first_point )
        continue;

      do
      {
        p = p->next;

        if ( contour == contour_index )
        {
          if ( p->x < contour_min_x )
            contour_min_x = p->x;
          if ( p->x > contour_max_x )
            contour_max_x = p->x;
        }
        else
        {
          if ( p->x < others_min_x )
            others_min_x = p->x;
          if ( p->x > others_max_x )
            others_max_x = p->x;
        }
      } while ( p != first_point );
    }

    horizontal_overlap =
      ( others_min_x <= contour_max_x && contour_max_x <= others_max_x ) ||
      ( others_min_x <= contour_min_x && contour_min_x <= others_max_x ) ||
      ( contour_max_x >= others_max_x && contour_min_x <= others_min_x );

    return horizontal_overlap;
  }


  static void
  af_glyph_hints_apply_vertical_separation_adjustments(
    AF_GlyphHints  hints,
    AF_Dimension   dim,
    FT_UInt        glyph_index,
    FT_Pos         accent_height_limit,
    FT_Hash        reverse_charmap )
  {
    FT_Bool  adjust_top       = FALSE;
    FT_Bool  adjust_below_top = FALSE;

    FT_Bool  adjust_bottom       = FALSE;
    FT_Bool  adjust_above_bottom = FALSE;

    size_t*    val;
    FT_UInt32  adj_type = AF_ADJUST_NONE;


    FT_TRACE4(( "Entering"
                " af_glyph_hints_apply_vertical_separation_adjustments\n" ));

    if ( dim != AF_DIMENSION_VERT )
      return;

    val = ft_hash_num_lookup( (FT_Int)glyph_index, reverse_charmap );
    if ( val )
    {
      FT_UInt  codepoint = *val;


      adj_type = af_adjustment_database_lookup( codepoint );

      if ( adj_type )
      {
        adjust_top       = !!( adj_type & AF_ADJUST_UP );
        adjust_below_top = !!( adj_type & AF_ADJUST_UP2 );

        adjust_bottom       = !!( adj_type & AF_ADJUST_DOWN );
        adjust_above_bottom = !!( adj_type & AF_ADJUST_DOWN2 );
      }
    }

    if ( ( ( adjust_top || adjust_bottom ) &&
           hints->num_contours >= 2        )             ||
         ( ( adjust_below_top || adjust_above_bottom ) &&
           hints->num_contours >= 3                    ) )
    {
      /* Recompute vertical extrema, this time acting on already */
      /* auto-hinted outlines.                                   */
      af_compute_vertical_extrema( hints );
    }

    if ( ( adjust_top && hints->num_contours >= 2 )       ||
         ( adjust_below_top && hints->num_contours >= 3 ) )
    {
      FT_Int  high_contour;
      FT_Pos  high_min_y;
      FT_Pos  high_max_y;
      FT_Pos  high_height;

      FT_Int  tilde_contour;
      FT_Pos  tilde_min_y;
      FT_Pos  tilde_max_y;
      FT_Pos  tilde_height;

      FT_Int   contour;
      FT_Bool  horizontal_overlap;

      FT_Pos  min_distance         = 64;
      FT_Pos  adjustment_amount;
      FT_Pos  calculated_amount;
      FT_Pos  centering_adjustment = 0;
      FT_Pos  pos;

      FT_Bool  is_top_tilde       = !!( adj_type & AF_ADJUST_TILDE_TOP );
      FT_Bool  is_below_top_tilde = !!( adj_type & AF_ADJUST_TILDE_TOP2 );


      FT_TRACE4(( "af_glyph_hints_apply_vertical_separation_adjustments:\n"
                  "  Applying vertical adjustment: %s\n",
                  adjust_top ? "AF_ADJUST_TOP" : "AF_ADJUST_TOP2" ));

      high_contour = adjust_below_top
                       ? af_find_second_highest_contour( hints )
                       : af_find_highest_contour( hints );

      /* Check for a horizontal overlap between the high contour and the */
      /* rest.  If there is no overlap, do not adjust.                   */
      horizontal_overlap =
        af_check_contour_horizontal_overlap( hints, high_contour );
      if ( !horizontal_overlap )
      {
        FT_TRACE4(( "    High contour does not horizontally overlap"
                    " with other contours.\n"
                    "    Skipping adjustment.\n" ));
        return;
      }

      high_min_y  = hints->contour_y_minima[high_contour];
      high_max_y  = hints->contour_y_maxima[high_contour];
      high_height = high_max_y - high_min_y;

      if ( high_height > accent_height_limit )
      {
        FT_TRACE4(( "    High contour height (%.2f) exceeds accent height"
                    " limit (%.2f).\n"
                    "    Skipping adjustment.\n",
                    (double)high_height / 64,
                    (double)accent_height_limit / 64 ));
        return;
      }

      /* If the difference between the vertical minimum of the high   */
      /* contour and the vertical maximum of another contour is less  */
      /* than a pixel, shift up the high contour to make the distance */
      /* one pixel.                                                   */
      for ( contour = 0; contour < hints->num_contours; contour++ )
      {
        FT_Pos  min_y;
        FT_Pos  max_y;
        FT_Pos  distance;


        if ( contour == high_contour )
          continue;

        min_y = hints->contour_y_minima[contour];
        max_y = hints->contour_y_maxima[contour];

        /* We also check that the y minimum of the 'other' contour */
        /* is below the high contour to avoid potential false hits */
        /* with contours enclosed in the high one.                 */
        distance = high_min_y - max_y;
        if ( distance < 64           &&
             distance < min_distance &&
             min_y < high_min_y      )
          min_distance = distance;
      }

      adjustment_amount = 64 - min_distance;

      if ( is_top_tilde || is_below_top_tilde )
      {
        tilde_contour = adjust_top
                          ? high_contour
                          : ( is_below_top_tilde
                                ? high_contour
                                : af_find_highest_contour( hints ) );

        tilde_min_y  = hints->contour_y_minima[tilde_contour];
        tilde_max_y  = hints->contour_y_maxima[tilde_contour];
        tilde_height = tilde_max_y - tilde_min_y;

        /* The vertical separation adjustment potentially undoes a */
        /* tilde center alignment.  If it would grid-align a tilde */
        /* less than 3 pixels in height, shift additionally to     */
        /* re-center the tilde.                                    */

        pos = high_min_y + adjustment_amount;
        if ( adjust_below_top && is_top_tilde )
          pos += high_height;

        if ( pos % 64 == 0 && tilde_height < 3 * 64 )
        {
          centering_adjustment = ( FT_PIX_ROUND( tilde_height ) -
                                   tilde_height ) / 2;

          FT_TRACE4(( "    Additional tilde centering adjustment: %ld\n",
                      centering_adjustment ));
        }
      }

      if ( ( adjust_top && is_top_tilde )             ||
           ( adjust_below_top && is_below_top_tilde ) )
        calculated_amount = adjustment_amount + centering_adjustment;
      else
        calculated_amount = adjustment_amount;

      /* allow a delta of 2/64px to handle rounding differences */
      FT_TRACE4(( "    Calculated adjustment amount: %ld%s\n",
                  calculated_amount,
                  ( calculated_amount < -2                               ||
                    ( adjustment_amount > 66 && calculated_amount > 66 ) )
                      ? " (out of range [-2;66], not adjusting)" : "" ));

      if ( calculated_amount != 0                                 &&
           calculated_amount >= -2                                &&
           ( calculated_amount <= 66 || adjustment_amount <= 66 ) )
      {
        /* Value 8 is heuristic. */
        FT_Pos  height_delta = high_height / 8;
        FT_Pos  min_y_limit  = high_min_y - height_delta;


        FT_TRACE4(( "    Pushing high contour %ld units up\n",
                    calculated_amount ));

        /* While we use only a single contour (the 'high' one) for    */
        /* computing `adjustment_amount`, we apply it to all contours */
        /* that are (approximately) in the same vertical range or     */
        /* higher.  This covers, for example, the inner contour of    */
        /* the Czech ring accent or the second acute accent in the    */
        /* Hungarian double acute accent.                             */
        af_move_contours_up( hints, min_y_limit, adjustment_amount );

        if ( adjust_below_top && is_top_tilde )
        {
          FT_TRACE4(( "    Pushing top tilde %ld units up\n",
                      centering_adjustment ));

          af_move_contours_up( hints,
                               min_y_limit + high_height,
                               centering_adjustment );
        }
      }
    }

    if ( ( adjust_bottom && hints->num_contours >= 2 )       ||
         ( adjust_above_bottom && hints->num_contours >= 3 ) )
    {
      FT_Int  low_contour;
      FT_Pos  low_min_y;
      FT_Pos  low_max_y;
      FT_Pos  low_height;

      FT_Int  tilde_contour;
      FT_Pos  tilde_min_y;
      FT_Pos  tilde_max_y;
      FT_Pos  tilde_height;

      FT_Int   contour;
      FT_Bool  horizontal_overlap;

      FT_Pos  min_distance         = 64;
      FT_Pos  adjustment_amount;
      FT_Pos  calculated_amount;
      FT_Pos  centering_adjustment = 0;
      FT_Pos  pos;

      FT_Bool  is_bottom_tilde =
                 !!( adj_type & AF_ADJUST_TILDE_BOTTOM );
      FT_Bool  is_above_bottom_tilde =
                 !!( adj_type & AF_ADJUST_TILDE_BOTTOM2 );


      FT_TRACE4(( "af_glyph_hints_apply_vertical_separation_adjustments:\n"
                  "  Applying vertical adjustment: %s\n",
                  adjust_bottom ? "AF_ADJUST_DOWN": "AF_ADJUST_DOWN2" ));

      low_contour = adjust_above_bottom
                      ? af_find_second_lowest_contour( hints )
                      : af_find_lowest_contour( hints );

      horizontal_overlap =
        af_check_contour_horizontal_overlap( hints, low_contour );
      if ( !horizontal_overlap )
      {
        FT_TRACE4(( "    Low contour does not horizontally overlap"
                    " with other contours.\n"
                    "    Skipping adjustment.\n" ));
        return;
      }

      low_min_y  = hints->contour_y_minima[low_contour];
      low_max_y  = hints->contour_y_maxima[low_contour];
      low_height = low_max_y - low_min_y;

      if ( low_height > accent_height_limit )
      {
        FT_TRACE4(( "    Low contour height (%.2f) exceeds accent height"
                    " limit (%.2f).\n"
                    "    Skipping adjustment.\n",
                    (double)low_height / 64,
                    (double)accent_height_limit / 64 ));
        return;
      }

      for ( contour = 0; contour < hints->num_contours; contour++ )
      {
        FT_Pos  min_y;
        FT_Pos  max_y;
        FT_Pos  distance;


        if ( contour == low_contour )
          continue;

        min_y = hints->contour_y_minima[contour];
        max_y = hints->contour_y_maxima[contour];

        distance = min_y - low_max_y;
        if ( distance < 64           &&
             distance < min_distance &&
             max_y > low_max_y       )
          min_distance = distance;
      }

      adjustment_amount = 64 - min_distance;

      if ( is_bottom_tilde || is_above_bottom_tilde )
      {
        tilde_contour = adjust_bottom
                          ? low_contour
                          : ( is_above_bottom_tilde
                                ? low_contour
                                : af_find_lowest_contour( hints ) );

        tilde_min_y  = hints->contour_y_minima[tilde_contour];
        tilde_max_y  = hints->contour_y_maxima[tilde_contour];
        tilde_height = tilde_max_y - tilde_min_y;

        pos = low_max_y - adjustment_amount;
        if ( adjust_above_bottom && is_bottom_tilde )
          pos -= low_height;

        if ( pos % 64 == 0 && tilde_height < 3 * 64 )
        {
          centering_adjustment = ( FT_PIX_ROUND( tilde_height ) -
                                   tilde_height ) / 2;

          FT_TRACE4(( "    Additional tilde centering adjustment: %ld\n",
                      centering_adjustment ));
        }
      }

      if ( ( adjust_bottom && is_bottom_tilde )             ||
           ( adjust_above_bottom && is_above_bottom_tilde ) )
        calculated_amount = adjustment_amount + centering_adjustment;
      else
        calculated_amount = adjustment_amount;

      FT_TRACE4(( "    Calculated adjustment amount: %ld%s\n",
                  calculated_amount,
                  ( calculated_amount < -2                               ||
                    ( adjustment_amount > 66 && calculated_amount > 66 ) )
                      ? " (out of range [-2;66], not adjusting)" : "" ));

      if ( calculated_amount != 0                                 &&
           calculated_amount >= -2                                &&
           ( calculated_amount <= 66 || adjustment_amount <= 66 ) )
      {
        FT_Pos  height_delta = low_height / 8;
        FT_Pos  max_y_limit  = low_max_y + height_delta;


        FT_TRACE4(( "    Pushing low contour %ld units down\n",
                    calculated_amount ));

        af_move_contours_down( hints, max_y_limit, adjustment_amount );

        if ( adjust_above_bottom && is_bottom_tilde )
        {
          FT_TRACE4(( "    Pushing bottom tilde %ld units down\n",
                      centering_adjustment ));

          af_move_contours_down( hints,
                                 max_y_limit - low_height,
                                 centering_adjustment );
        }
      }
    }

#ifdef FT_DEBUG_LEVEL_TRACE
    if ( !( ( ( adjust_top || adjust_bottom ) &&
              hints->num_contours >= 2        )             ||
            ( ( adjust_below_top || adjust_above_bottom ) &&
              hints->num_contours >= 3                    ) ) )
      FT_TRACE4(( "af_glyph_hints_apply_vertical_separation_adjustments:\n"
                  "  No vertical adjustment applied\n" ));
#endif

    FT_TRACE4(( "Exiting"
                " af_glyph_hints_apply_vertical_separation_adjustments\n" ));
  }


#undef  FT_COMPONENT
#define FT_COMPONENT  aflatin


  /* Compute the snapped width of a given stem, ignoring very thin ones. */
  /* There is a lot of voodoo in this function; changing the hard-coded  */
  /* parameters influence the whole hinting process.                     */

  static FT_Pos
  af_latin_compute_stem_width( AF_GlyphHints  hints,
                               AF_Dimension   dim,
                               FT_Pos         width,
                               FT_Pos         base_delta,
                               FT_UInt        base_flags,
                               FT_UInt        stem_flags )
  {
    AF_LatinMetrics  metrics  = (AF_LatinMetrics)hints->metrics;
    AF_LatinAxis     axis     = &metrics->axis[dim];
    FT_Pos           dist     = width;
    FT_Int           sign     = 0;
    FT_Int           vertical = ( dim == AF_DIMENSION_VERT );


    if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) ||
         axis->extra_light                       )
      return width;

    if ( dist < 0 )
    {
      dist = -width;
      sign = 1;
    }

    if ( (  vertical && !AF_LATIN_HINTS_DO_VERT_SNAP( hints ) ) ||
         ( !vertical && !AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) ) )
    {
      /* smooth hinting process: very lightly quantize the stem width */

      /* leave the widths of serifs alone */
      if ( ( stem_flags & AF_EDGE_SERIF ) &&
           vertical                       &&
           ( dist < 3 * 64 )              )
        goto Done_Width;

      else if ( base_flags & AF_EDGE_ROUND )
      {
        if ( dist < 80 )
          dist = 64;
      }
      else if ( dist < 56 )
        dist = 56;

      if ( axis->width_count > 0 )
      {
        FT_Pos  delta;


        /* compare to standard width */
        delta = dist - axis->widths[0].cur;

        if ( delta < 0 )
          delta = -delta;

        if ( delta < 40 )
        {
          dist = axis->widths[0].cur;
          if ( dist < 48 )
            dist = 48;

          goto Done_Width;
        }

        if ( dist < 3 * 64 )
        {
          delta  = dist & 63;
          dist  &= -64;

          if ( delta < 10 )
            dist += delta;

          else if ( delta < 32 )
            dist += 10;

          else if ( delta < 54 )
            dist += 54;

          else
            dist += delta;
        }
        else
        {
          /* A stem's end position depends on two values: the start        */
          /* position and the stem length.  The former gets usually        */
          /* rounded to the grid, while the latter gets rounded also if it */
          /* exceeds a certain length (see below in this function).  This  */
          /* `double rounding' can lead to a great difference to the       */
          /* original, unhinted position; this normally doesn't matter for */
          /* large PPEM values, but for small sizes it can easily make     */
          /* outlines collide.  For this reason, we adjust the stem length */
          /* by a small amount depending on the PPEM value in case the     */
          /* former and latter rounding both point into the same           */
          /* direction.                                                    */

          FT_Pos  bdelta = 0;


          if ( ( ( width > 0 ) && ( base_delta > 0 ) ) ||
               ( ( width < 0 ) && ( base_delta < 0 ) ) )
          {
            FT_UInt  ppem = metrics->root.scaler.face->size->metrics.x_ppem;


            if ( ppem < 10 )
              bdelta = base_delta;
            else if ( ppem < 30 )
              bdelta = ( base_delta * (FT_Pos)( 30 - ppem ) ) / 20;

            if ( bdelta < 0 )
              bdelta = -bdelta;
          }

          dist = ( dist - bdelta + 32 ) & ~63;
        }
      }
    }
    else
    {
      /* strong hinting process: snap the stem width to integer pixels */

      FT_Pos  org_dist = dist;


      dist = af_latin_snap_width( axis->widths, axis->width_count, dist );

      if ( vertical )
      {
        /* in the case of vertical hinting, always round */
        /* the stem heights to integer pixels            */

        if ( dist >= 64 )
          dist = ( dist + 16 ) & ~63;
        else
          dist = 64;
      }
      else
      {
        if ( AF_LATIN_HINTS_DO_MONO( hints ) )
        {
          /* monochrome horizontal hinting: snap widths to integer pixels */
          /* with a different threshold                                   */

          if ( dist < 64 )
            dist = 64;
          else
            dist = ( dist + 32 ) & ~63;
        }
        else
        {
          /* for horizontal anti-aliased hinting, we adopt a more subtle */
          /* approach: we strengthen small stems, round stems whose size */
          /* is between 1 and 2 pixels to an integer, otherwise nothing  */

          if ( dist < 48 )
            dist = ( dist + 64 ) >> 1;

          else if ( dist < 128 )
          {
            /* We only round to an integer width if the corresponding */
            /* distortion is less than 1/4 pixel.  Otherwise this     */
            /* makes everything worse since the diagonals, which are  */
            /* not hinted, appear a lot bolder or thinner than the    */
            /* vertical stems.                                        */

            FT_Pos  delta;


            dist = ( dist + 22 ) & ~63;
            delta = dist - org_dist;
            if ( delta < 0 )
              delta = -delta;

            if ( delta >= 16 )
            {
              dist = org_dist;
              if ( dist < 48 )
                dist = ( dist + 64 ) >> 1;
            }
          }
          else
            /* round otherwise to prevent color fringes in LCD mode */
            dist = ( dist + 32 ) & ~63;
        }
      }
    }

  Done_Width:
    if ( sign )
      dist = -dist;

    return dist;
  }


  /* Align one stem edge relative to the previous stem edge. */

  static void
  af_latin_align_linked_edge( AF_GlyphHints  hints,
                              AF_Dimension   dim,
                              AF_Edge        base_edge,
                              AF_Edge        stem_edge )
  {
    FT_Pos  dist, base_delta;
    FT_Pos  fitted_width;


    dist       = stem_edge->opos - base_edge->opos;
    base_delta = base_edge->pos - base_edge->opos;

    fitted_width = af_latin_compute_stem_width( hints, dim,
                                                dist, base_delta,
                                                base_edge->flags,
                                                stem_edge->flags );


    stem_edge->pos = base_edge->pos + fitted_width;

    FT_TRACE5(( "  LINK: edge %td (opos=%.2f) linked to %.2f,"
                " dist was %.2f, now %.2f\n",
                stem_edge - hints->axis[dim].edges,
                (double)stem_edge->opos / 64, (double)stem_edge->pos / 64,
                (double)dist / 64, (double)fitted_width / 64 ));
  }


  /* Shift the coordinates of the `serif' edge by the same amount */
  /* as the corresponding `base' edge has been moved already.     */

  static void
  af_latin_align_serif_edge( AF_GlyphHints  hints,
                             AF_Edge        base,
                             AF_Edge        serif )
  {
    FT_UNUSED( hints );

    serif->pos = base->pos + ( serif->opos - base->opos );
  }


  /*************************************************************************/
  /*************************************************************************/
  /*************************************************************************/
  /****                                                                 ****/
  /****                    E D G E   H I N T I N G                      ****/
  /****                                                                 ****/
  /*************************************************************************/
  /*************************************************************************/
  /*************************************************************************/


  /* The main grid-fitting routine. */

  static void
  af_latin_hint_edges( AF_GlyphHints  hints,
                       AF_Dimension   dim )
  {
    AF_AxisHints  axis = &hints->axis[dim];

    AF_Edge     edges      = axis->edges;
    AF_Edge     edge_limit = FT_OFFSET( edges, axis->num_edges );
    AF_Edge     edge;
    FT_PtrDist  n_edges;

    AF_Edge  anchor             = NULL;
    FT_Bool  has_non_stem_edges = 0;

    AF_StyleClass   style_class  = hints->metrics->style_class;
    AF_ScriptClass  script_class = af_script_classes[style_class->script];

    FT_Bool  top_to_bottom_hinting = 0;

#ifdef FT_DEBUG_LEVEL_TRACE
    FT_UInt  num_actions = 0;
#endif


    FT_TRACE5(( "latin %s edge hinting (style `%s')\n",
                dim == AF_DIMENSION_VERT ? "horizontal" : "vertical",
                af_style_names[hints->metrics->style_class->style] ));

    if ( dim == AF_DIMENSION_VERT )
      top_to_bottom_hinting = script_class->top_to_bottom_hinting;

    /* we begin by aligning all stems relative to the blue zone */
    /* if needed -- that's only for horizontal edges            */

    if ( dim == AF_DIMENSION_VERT && AF_HINTS_DO_BLUES( hints ) )
    {
      for ( edge = edges; edge < edge_limit; edge++ )
      {
        AF_Width  blue;
        AF_Edge   edge1, edge2; /* these edges form the stem to check */


        if ( edge->flags & AF_EDGE_DONE )
          continue;

        edge1 = NULL;
        edge2 = edge->link;

        /*
         * If a stem contains both a neutral and a non-neutral blue zone,
         * skip the neutral one.  Otherwise, outlines with different
         * directions might be incorrectly aligned at the same vertical
         * position.
         *
         * If we have two neutral blue zones, skip one of them.
         *
         */
        if ( edge->blue_edge && edge2 && edge2->blue_edge )
        {
          FT_Byte  neutral  = edge->flags  & AF_EDGE_NEUTRAL;
          FT_Byte  neutral2 = edge2->flags & AF_EDGE_NEUTRAL;


          if ( neutral2 )
          {
            edge2->blue_edge = NULL;
            edge2->flags    &= ~AF_EDGE_NEUTRAL;
          }
          else if ( neutral )
          {
            edge->blue_edge = NULL;
            edge->flags    &= ~AF_EDGE_NEUTRAL;
          }
        }

        blue = edge->blue_edge;
        if ( blue )
          edge1 = edge;

        /* flip edges if the other edge is aligned to a blue zone */
        else if ( edge2 && edge2->blue_edge )
        {
          blue  = edge2->blue_edge;
          edge1 = edge2;
          edge2 = edge;
        }

        if ( !edge1 )
          continue;

#ifdef FT_DEBUG_LEVEL_TRACE
        if ( !anchor )
          FT_TRACE5(( "  BLUE_ANCHOR: edge %td (opos=%.2f) snapped to %.2f,"
                      " was %.2f (anchor=edge %td)\n",
                      edge1 - edges,
                      (double)edge1->opos / 64, (double)blue->fit / 64,
                      (double)edge1->pos / 64, edge - edges ));
        else
          FT_TRACE5(( "  BLUE: edge %td (opos=%.2f) snapped to %.2f,"
                      " was %.2f\n",
                      edge1 - edges,
                      (double)edge1->opos / 64, (double)blue->fit / 64,
                      (double)edge1->pos / 64 ));

        num_actions++;
#endif

        edge1->pos    = blue->fit;
        edge1->flags |= AF_EDGE_DONE;

        if ( edge2 && !edge2->blue_edge )
        {
          af_latin_align_linked_edge( hints, dim, edge1, edge2 );
          edge2->flags |= AF_EDGE_DONE;

#ifdef FT_DEBUG_LEVEL_TRACE
          num_actions++;
#endif
        }

        if ( !anchor )
          anchor = edge;
      }
    }

    /* now we align all other stem edges, trying to maintain the */
    /* relative order of stems in the glyph                      */
    for ( edge = edges; edge < edge_limit; edge++ )
    {
      AF_Edge  edge2;


      if ( edge->flags & AF_EDGE_DONE )
        continue;

      /* skip all non-stem edges */
      edge2 = edge->link;
      if ( !edge2 )
      {
        has_non_stem_edges = TRUE;
        continue;
      }

      /* now align the stem */

      /* this should not happen, but it's better to be safe */
      if ( edge2->blue_edge )
      {
        FT_TRACE5(( "  ASSERTION FAILED for edge %td\n", edge2 - edges ));

        af_latin_align_linked_edge( hints, dim, edge2, edge );
        edge->flags |= AF_EDGE_DONE;

#ifdef FT_DEBUG_LEVEL_TRACE
        num_actions++;
#endif
        continue;
      }

      if ( !anchor )
      {
        /* if we reach this if clause, no stem has been aligned yet */

        FT_Pos  org_len, org_center, cur_len;
        FT_Pos  cur_pos1, error1, error2, u_off, d_off;


        org_len = edge2->opos - edge->opos;
        cur_len = af_latin_compute_stem_width( hints, dim,
                                               org_len, 0,
                                               edge->flags,
                                               edge2->flags );

        /* some voodoo to specially round edges for small stem widths; */
        /* the idea is to align the center of a stem, then shifting    */
        /* the stem edges to suitable positions                        */
        if ( cur_len <= 64 )
        {
          /* width <= 1px */
          u_off = 32;
          d_off = 32;
        }
        else
        {
          /* 1px < width < 1.5px */
          u_off = 38;
          d_off = 26;
        }

        if ( cur_len < 96 )
        {
          org_center = edge->opos + ( org_len >> 1 );
          cur_pos1   = FT_PIX_ROUND( org_center );

          error1 = org_center - ( cur_pos1 - u_off );
          if ( error1 < 0 )
            error1 = -error1;

          error2 = org_center - ( cur_pos1 + d_off );
          if ( error2 < 0 )
            error2 = -error2;

          if ( error1 < error2 )
            cur_pos1 -= u_off;
          else
            cur_pos1 += d_off;

          edge->pos  = cur_pos1 - cur_len / 2;
          edge2->pos = edge->pos + cur_len;
        }
        else
          edge->pos = FT_PIX_ROUND( edge->opos );

        anchor       = edge;
        edge->flags |= AF_EDGE_DONE;

        FT_TRACE5(( "  ANCHOR: edge %td (opos=%.2f) and %td (opos=%.2f)"
                    " snapped to %.2f and %.2f\n",
                    edge - edges, (double)edge->opos / 64,
                    edge2 - edges, (double)edge2->opos / 64,
                    (double)edge->pos / 64, (double)edge2->pos / 64 ));

        af_latin_align_linked_edge( hints, dim, edge, edge2 );

#ifdef FT_DEBUG_LEVEL_TRACE
        num_actions += 2;
#endif
      }
      else
      {
        FT_Pos  org_pos, org_len, org_center, cur_len;
        FT_Pos  cur_pos1, cur_pos2, delta1, delta2;


        org_pos    = anchor->pos + ( edge->opos - anchor->opos );
        org_len    = edge2->opos - edge->opos;
        org_center = org_pos + ( org_len >> 1 );

        cur_len = af_latin_compute_stem_width( hints, dim,
                                               org_len, 0,
                                               edge->flags,
                                               edge2->flags );

        if ( edge2->flags & AF_EDGE_DONE )
        {
          FT_TRACE5(( "  ADJUST: edge %td (pos=%.2f) moved to %.2f\n",
                      edge - edges, (double)edge->pos / 64,
                      (double)( edge2->pos - cur_len ) / 64 ));

          edge->pos = edge2->pos - cur_len;
        }

        else if ( cur_len < 96 )
        {
          FT_Pos  u_off, d_off;


          cur_pos1 = FT_PIX_ROUND( org_center );

          if ( cur_len <= 64 )
          {
            u_off = 32;
            d_off = 32;
          }
          else
          {
            u_off = 38;
            d_off = 26;
          }

          delta1 = org_center - ( cur_pos1 - u_off );
          if ( delta1 < 0 )
            delta1 = -delta1;

          delta2 = org_center - ( cur_pos1 + d_off );
          if ( delta2 < 0 )
            delta2 = -delta2;

          if ( delta1 < delta2 )
            cur_pos1 -= u_off;
          else
            cur_pos1 += d_off;

          edge->pos  = cur_pos1 - cur_len / 2;
          edge2->pos = cur_pos1 + cur_len / 2;

          FT_TRACE5(( "  STEM: edge %td (opos=%.2f) linked to %td (opos=%.2f)"
                      " snapped to %.2f and %.2f\n",
                      edge - edges, (double)edge->opos / 64,
                      edge2 - edges, (double)edge2->opos / 64,
                      (double)edge->pos / 64, (double)edge2->pos / 64 ));
        }

        else
        {
          org_pos    = anchor->pos + ( edge->opos - anchor->opos );
          org_len    = edge2->opos - edge->opos;
          org_center = org_pos + ( org_len >> 1 );

          cur_len    = af_latin_compute_stem_width( hints, dim,
                                                    org_len, 0,
                                                    edge->flags,
                                                    edge2->flags );

          cur_pos1 = FT_PIX_ROUND( org_pos );
          delta1   = cur_pos1 + ( cur_len >> 1 ) - org_center;
          if ( delta1 < 0 )
            delta1 = -delta1;

          cur_pos2 = FT_PIX_ROUND( org_pos + org_len ) - cur_len;
          delta2   = cur_pos2 + ( cur_len >> 1 ) - org_center;
          if ( delta2 < 0 )
            delta2 = -delta2;

          edge->pos  = ( delta1 < delta2 ) ? cur_pos1 : cur_pos2;
          edge2->pos = edge->pos + cur_len;

          FT_TRACE5(( "  STEM: edge %td (opos=%.2f) linked to %td (opos=%.2f)"
                      " snapped to %.2f and %.2f\n",
                      edge - edges, (double)edge->opos / 64,
                      edge2 - edges, (double)edge2->opos / 64,
                      (double)edge->pos / 64, (double)edge2->pos / 64 ));
        }

#ifdef FT_DEBUG_LEVEL_TRACE
        num_actions++;
#endif

        edge->flags  |= AF_EDGE_DONE;
        edge2->flags |= AF_EDGE_DONE;

        if ( edge > edges                                             &&
             ( top_to_bottom_hinting ? ( edge->pos > edge[-1].pos )
                                     : ( edge->pos < edge[-1].pos ) ) )
        {
          /* don't move if stem would (almost) disappear otherwise; */
          /* the ad-hoc value 16 corresponds to 1/4px               */
          if ( edge->link && FT_ABS( edge->link->pos - edge[-1].pos ) > 16 )
          {
#ifdef FT_DEBUG_LEVEL_TRACE
            FT_TRACE5(( "  BOUND: edge %td (pos=%.2f) moved to %.2f\n",
                        edge - edges,
                        (double)edge->pos / 64,
                        (double)edge[-1].pos / 64 ));

            num_actions++;
#endif

            edge->pos = edge[-1].pos;
          }
        }
      }
    }

    /* make sure that lowercase m's maintain their symmetry */

    /* In general, lowercase m's have six vertical edges if they are sans */
    /* serif, or twelve if they are with serifs.  This implementation is  */
    /* based on that assumption, and seems to work very well with most    */
    /* faces.  However, if for a certain face this assumption is not      */
    /* true, the m is just rendered like before.  In addition, any stem   */
    /* correction will only be applied to symmetrical glyphs (even if the */
    /* glyph is not an m), so the potential for unwanted distortion is    */
    /* relatively low.                                                    */

    /* We don't handle horizontal edges since we can't easily assure that */
    /* the third (lowest) stem aligns with the base line; it might end up */
    /* one pixel higher or lower.                                         */

    n_edges = edge_limit - edges;
    if ( dim == AF_DIMENSION_HORZ && ( n_edges == 6 || n_edges == 12 ) )
    {
      AF_Edge  edge1, edge2, edge3;
      FT_Pos   dist1, dist2, span, delta;


      if ( n_edges == 6 )
      {
        edge1 = edges;
        edge2 = edges + 2;
        edge3 = edges + 4;
      }
      else
      {
        edge1 = edges + 1;
        edge2 = edges + 5;
        edge3 = edges + 9;
      }

      dist1 = edge2->opos - edge1->opos;
      dist2 = edge3->opos - edge2->opos;

      span = dist1 - dist2;
      if ( span < 0 )
        span = -span;

      if ( span < 8 )
      {
        delta = edge3->pos - ( 2 * edge2->pos - edge1->pos );
        edge3->pos -= delta;
        if ( edge3->link )
          edge3->link->pos -= delta;

        /* move the serifs along with the stem */
        if ( n_edges == 12 )
        {
          ( edges + 8 )->pos -= delta;
          ( edges + 11 )->pos -= delta;
        }

        edge3->flags |= AF_EDGE_DONE;
        if ( edge3->link )
          edge3->link->flags |= AF_EDGE_DONE;
      }
    }

    if ( has_non_stem_edges || !anchor )
    {
      /*
       * now hint the remaining edges (serifs and single) in order
       * to complete our processing
       */
      for ( edge = edges; edge < edge_limit; edge++ )
      {
        FT_Pos  delta;


        if ( edge->flags & AF_EDGE_DONE )
          continue;

        delta = 1000;

        if ( edge->serif )
        {
          AF_Edge  e, top, bottom;
          FT_Pos   min_pos, max_pos;


          /* Check whether we have a real serif -- if there are  */
          /* other edges with overlapping (or enclosed) segments */
          /* between the primary and serif edge, we have not.    */
          /*                                                     */
          /* Such a situation might happen if an accent is very  */
          /* near to its base glyph (for example, Vietnamese     */
          /* uppercase letters with two accents in `arial.ttf`), */
          /* and the segment detection algorithm classifies the  */
          /* top of the accent incorrectly as a serif.           */
          delta = edge->serif->opos - edge->opos;
          if ( delta < 0 )
          {
            delta = -delta;

            top    = edge;
            bottom = edge->serif;
          }
          else
          {
            top    = edge->serif;
            bottom = edge;
          }

          if ( delta < 64 + 32 )
          {
            /* take care of outline orientation while computing extrema */
            min_pos = FT_MIN( FT_MIN( FT_MIN( top->first->first->v,
                                              top->first->last->v ),
                                      FT_MIN( top->last->first->v,
                                              top->last->last->v ) ),
                              FT_MIN( FT_MIN( bottom->first->first->v,
                                              bottom->first->last->v ),
                                      FT_MIN( bottom->last->first->v,
                                              bottom->last->last->v ) ) );
            max_pos = FT_MAX( FT_MAX( FT_MAX( top->first->first->v,
                                              top->first->last->v ),
                                      FT_MAX( top->last->first->v,
                                              top->last->last->v ) ),
                              FT_MAX( FT_MAX( bottom->first->first->v,
                                              bottom->first->last->v ),
                                      FT_MAX( bottom->last->first->v,
                                              bottom->last->last->v ) ) );

            for ( e = bottom + 1; e < top; e++ )
            {
              FT_Pos  e_min = FT_MIN( FT_MIN( e->first->first->v,
                                              e->first->last->v ),
                                      FT_MIN( e->last->first->v,
                                              e->last->last->v ) );
              FT_Pos  e_max = FT_MAX( FT_MAX( e->first->first->v,
                                              e->first->last->v ),
                                      FT_MAX( e->last->first->v,
                                              e->last->last->v ) );

              if ( !( ( e_min < min_pos && e_max < min_pos ) ||
                      ( e_min > max_pos && e_max > max_pos ) ) )
              {
                delta = 1000;  /* not a real serif */
                break;
              }
            }

            if ( delta == 1000 )
              continue;
          }
        }

        if ( delta < 64 + 16 )
        {
          af_latin_align_serif_edge( hints, edge->serif, edge );
          FT_TRACE5(( "  SERIF: edge %td (opos=%.2f) serif to %td (opos=%.2f)"
                      " aligned to %.2f\n",
                      edge - edges, (double)edge->opos / 64,
                      edge->serif - edges, (double)edge->serif->opos / 64,
                      (double)edge->pos / 64 ));
        }
        else if ( !anchor )
        {
          edge->pos = FT_PIX_ROUND( edge->opos );
          anchor    = edge;
          FT_TRACE5(( "  SERIF_ANCHOR: edge %td (opos=%.2f)"
                      " snapped to %.2f\n",
                      edge - edges,
                      (double)edge->opos / 64, (double)edge->pos / 64 ));
        }
        else
        {
          AF_Edge  before, after;


          for ( before = edge - 1; before >= edges; before-- )
            if ( before->flags & AF_EDGE_DONE )
              break;

          for ( after = edge + 1; after < edge_limit; after++ )
            if ( after->flags & AF_EDGE_DONE )
              break;

          if ( before >= edges && before < edge   &&
               after < edge_limit && after > edge )
          {
            if ( after->opos == before->opos )
              edge->pos = before->pos;
            else
              edge->pos = before->pos +
                          FT_MulDiv( edge->opos - before->opos,
                                     after->pos - before->pos,
                                     after->opos - before->opos );

            FT_TRACE5(( "  SERIF_LINK1: edge %td (opos=%.2f) snapped to %.2f"
                        " from %td (opos=%.2f)\n",
                        edge - edges, (double)edge->opos / 64,
                        (double)edge->pos / 64,
                        before - edges, (double)before->opos / 64 ));
          }
          else
          {
            edge->pos = anchor->pos +
                        ( ( edge->opos - anchor->opos + 16 ) & ~31 );
            FT_TRACE5(( "  SERIF_LINK2: edge %td (opos=%.2f)"
                        " snapped to %.2f\n",
                        edge - edges,
                        (double)edge->opos / 64, (double)edge->pos / 64 ));
          }
        }

#ifdef FT_DEBUG_LEVEL_TRACE
        num_actions++;
#endif
        edge->flags |= AF_EDGE_DONE;

        if ( edge > edges                                             &&
             ( top_to_bottom_hinting ? ( edge->pos > edge[-1].pos )
                                     : ( edge->pos < edge[-1].pos ) ) )
        {
          /* don't move if stem would (almost) disappear otherwise; */
          /* the ad-hoc value 16 corresponds to 1/4px               */
          if ( edge->link && FT_ABS( edge->link->pos - edge[-1].pos ) > 16 )
          {
#ifdef FT_DEBUG_LEVEL_TRACE
            FT_TRACE5(( "  BOUND: edge %td (pos=%.2f) moved to %.2f\n",
                        edge - edges,
                        (double)edge->pos / 64,
                        (double)edge[-1].pos / 64 ));

            num_actions++;
#endif
            edge->pos = edge[-1].pos;
          }
        }

        if ( edge + 1 < edge_limit                                   &&
             edge[1].flags & AF_EDGE_DONE                            &&
             ( top_to_bottom_hinting ? ( edge->pos < edge[1].pos )
                                     : ( edge->pos > edge[1].pos ) ) )
        {
          /* don't move if stem would (almost) disappear otherwise; */
          /* the ad-hoc value 16 corresponds to 1/4px               */
          if ( edge->link && FT_ABS( edge->link->pos - edge[-1].pos ) > 16 )
          {
#ifdef FT_DEBUG_LEVEL_TRACE
            FT_TRACE5(( "  BOUND: edge %td (pos=%.2f) moved to %.2f\n",
                        edge - edges,
                        (double)edge->pos / 64,
                        (double)edge[1].pos / 64 ));

            num_actions++;
#endif

            edge->pos = edge[1].pos;
          }
        }
      }
    }

#ifdef FT_DEBUG_LEVEL_TRACE
    if ( !num_actions )
      FT_TRACE5(( "  (none)\n" ));
    FT_TRACE5(( "\n" ));
#endif
  }


  /* Apply the complete hinting algorithm to a latin glyph. */

  static FT_Error
  af_latin_hints_apply( FT_UInt          glyph_index,
                        AF_GlyphHints    hints,
                        FT_Outline*      outline,
                        AF_StyleMetrics  metrics_ )    /* AF_LatinMetrics */
  {
    AF_LatinMetrics  metrics = (AF_LatinMetrics)metrics_;

    FT_Error  error;
    int       dim;

    AF_LatinAxis  axis;

    FT_Pos  accent_height_limit = 0;


    error = af_glyph_hints_reload( hints, outline );
    if ( error )
      goto Exit;

    /* analyze glyph outline */
    if ( AF_HINTS_DO_HORIZONTAL( hints ) )
    {
      axis  = &metrics->axis[AF_DIMENSION_HORZ];
      error = af_latin_hints_detect_features( hints,
                                              axis->width_count,
                                              axis->widths,
                                              AF_DIMENSION_HORZ );
      if ( error )
        goto Exit;
    }

    if ( AF_HINTS_DO_VERTICAL( hints ) )
    {
      size_t*  val;

      FT_Int  top_tilde_contour    = 0;
      FT_Int  bottom_tilde_contour = 0;

      FT_Int  below_top_tilde_contour    = 0;
      FT_Int  above_bottom_tilde_contour = 0;

      AF_LatinBlue  capital_top_blue    = NULL;
      AF_LatinBlue  capital_bottom_blue = NULL;

      AF_LatinBlue  small_top_blue    = NULL;
      AF_LatinBlue  small_bottom_blue = NULL;

      FT_Bool  have_flags = FALSE;

      FT_Bool  is_top_tilde    = FALSE;
      FT_Bool  is_bottom_tilde = FALSE;

      FT_Bool  is_below_top_tilde    = FALSE;
      FT_Bool  is_above_bottom_tilde = FALSE;

      FT_Bool  ignore_capital_top    = FALSE;
      FT_Bool  ignore_capital_bottom = FALSE;

      FT_Bool  ignore_small_top    = FALSE;
      FT_Bool  ignore_small_bottom = FALSE;

      FT_Bool  do_height_check = TRUE;

      FT_Pos  limit;
      FT_Pos  y_offset;


      val = ft_hash_num_lookup( (FT_Int)glyph_index,
                                metrics->root.reverse_charmap );
      if ( val )
      {
        FT_UInt    codepoint = *val;
        FT_UInt32  adj_type  = af_adjustment_database_lookup( codepoint );


        if ( adj_type )
        {
          have_flags = !!adj_type;

          is_top_tilde    = !!( adj_type & AF_ADJUST_TILDE_TOP );
          is_bottom_tilde = !!( adj_type & AF_ADJUST_TILDE_BOTTOM );

          is_below_top_tilde    = !!( adj_type & AF_ADJUST_TILDE_TOP2 );
          is_above_bottom_tilde = !!( adj_type & AF_ADJUST_TILDE_BOTTOM2 );

          ignore_capital_top    = !!( adj_type & AF_IGNORE_CAPITAL_TOP );
          ignore_capital_bottom = !!( adj_type & AF_IGNORE_CAPITAL_BOTTOM );

          ignore_small_top    = !!( adj_type & AF_IGNORE_SMALL_TOP );
          ignore_small_bottom = !!( adj_type & AF_IGNORE_SMALL_BOTTOM );

          do_height_check = !( adj_type & AF_ADJUST_NO_HEIGHT_CHECK );
        }
      }

      if ( is_top_tilde || is_bottom_tilde             ||
           is_below_top_tilde || is_above_bottom_tilde )
        af_compute_vertical_extrema( hints );

      /* Process inner tilde glyphs first. */
      if ( is_below_top_tilde )
      {
        below_top_tilde_contour = af_find_second_highest_contour( hints );

        y_offset = af_latin_stretch_top_tilde(
                     hints, below_top_tilde_contour );
        y_offset += af_latin_align_top_tilde(
                      hints, below_top_tilde_contour );

        limit = hints->contour_y_minima[below_top_tilde_contour];
        af_move_contours_up( hints, limit, y_offset );
      }
      if ( is_above_bottom_tilde )
      {
        above_bottom_tilde_contour = af_find_second_lowest_contour( hints );

        y_offset = af_latin_stretch_bottom_tilde(
                     hints, above_bottom_tilde_contour );
        y_offset -= af_latin_align_bottom_tilde(
                      hints, above_bottom_tilde_contour );

        limit = hints->contour_y_maxima[above_bottom_tilde_contour];
        af_move_contours_down( hints, limit, y_offset );
      }

      if ( is_top_tilde )
      {
        top_tilde_contour = af_find_highest_contour( hints );

        (void)af_latin_stretch_top_tilde( hints, top_tilde_contour );
        (void)af_latin_align_top_tilde( hints, top_tilde_contour );
      }
      if ( is_bottom_tilde )
      {
        bottom_tilde_contour = af_find_lowest_contour( hints );

        (void)af_latin_stretch_bottom_tilde( hints, bottom_tilde_contour );
        (void)af_latin_align_bottom_tilde( hints, bottom_tilde_contour );
      }

      axis  = &metrics->axis[AF_DIMENSION_VERT];
      error = af_latin_hints_detect_features( hints,
                                              axis->width_count,
                                              axis->widths,
                                              AF_DIMENSION_VERT );

      if ( have_flags )
      {
        af_latin_get_base_glyph_blues( hints,
                                       TRUE,
                                       &capital_top_blue,
                                       &capital_bottom_blue );
        af_latin_get_base_glyph_blues( hints,
                                       FALSE,
                                       &small_top_blue,
                                       &small_bottom_blue );

        if ( do_height_check )
        {
          /* Set a heuristic limit for the accent height so that    */
          /* `af_glyph_hints_apply_vertical_separation_adjustments` */
          /* can correctly ignore the case where an accent is       */
          /* unexpectedly not the highest (or lowest) contour.      */

          /* Either 2/3 of the lowercase blue zone height... */
          if ( small_top_blue && small_bottom_blue )
            accent_height_limit = 2 * ( small_top_blue->shoot.cur -
                                        small_bottom_blue->shoot.cur ) / 3;
          /* or 1/2 of the uppercase blue zone height... */
          else if ( capital_top_blue && capital_bottom_blue )
            accent_height_limit = ( capital_top_blue->shoot.cur -
                                    capital_bottom_blue->shoot.cur ) / 2;
          /* or half of the standard PostScript ascender value (8/10) */
          /* of the EM value, scaled.                                 */
          else
            accent_height_limit = FT_MulFix( metrics->units_per_em * 4 / 10,
                                             metrics->root.scaler.y_scale );
        }
      }

      if ( capital_top_blue && capital_bottom_blue )
      {
        if ( ignore_capital_top )
          af_latin_ignore_top( hints,
                               capital_top_blue, capital_bottom_blue );
        if ( ignore_capital_bottom )
          af_latin_ignore_bottom( hints,
                                  capital_top_blue, capital_bottom_blue );
      }
      if ( small_top_blue && small_bottom_blue )
      {
        if ( ignore_small_top )
          af_latin_ignore_top( hints,
                               small_top_blue, small_bottom_blue );
        if ( ignore_small_bottom )
          af_latin_ignore_bottom( hints,
                                  small_top_blue, small_bottom_blue );
      }

      if ( error )
        goto Exit;

      /* apply blue zones to base characters only */
      if ( !( metrics->root.globals->glyph_styles[glyph_index] & AF_NONBASE ) )
        af_latin_hints_compute_blue_edges( hints, metrics );
    }

    /* grid-fit the outline */
    for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
    {
      if ( ( dim == AF_DIMENSION_HORZ && AF_HINTS_DO_HORIZONTAL( hints ) ) ||
           ( dim == AF_DIMENSION_VERT && AF_HINTS_DO_VERTICAL( hints ) )   )
      {
        af_latin_hint_edges( hints, (AF_Dimension)dim );
        af_glyph_hints_align_edge_points( hints, (AF_Dimension)dim );
        af_glyph_hints_align_strong_points( hints, (AF_Dimension)dim );
        af_glyph_hints_align_weak_points( hints, (AF_Dimension)dim );
        af_glyph_hints_apply_vertical_separation_adjustments(
          hints,
          (AF_Dimension)dim,
          glyph_index,
          accent_height_limit,
          metrics->root.reverse_charmap );
      }
    }

    af_glyph_hints_save( hints, outline );

  Exit:
    return error;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****              L A T I N   S C R I P T   C L A S S              *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/


  AF_DEFINE_WRITING_SYSTEM_CLASS(
    af_latin_writing_system_class,

    AF_WRITING_SYSTEM_LATIN,

    sizeof ( AF_LatinMetricsRec ),

    (AF_WritingSystem_InitMetricsFunc) af_latin_metrics_init,        /* style_metrics_init    */
    (AF_WritingSystem_ScaleMetricsFunc)af_latin_metrics_scale,       /* style_metrics_scale   */
    (AF_WritingSystem_DoneMetricsFunc) af_latin_metrics_done,        /* style_metrics_done    */
    (AF_WritingSystem_GetStdWidthsFunc)af_latin_get_standard_widths, /* style_metrics_getstdw */

    (AF_WritingSystem_InitHintsFunc)   af_latin_hints_init,          /* style_hints_init      */
    (AF_WritingSystem_ApplyHintsFunc)  af_latin_hints_apply          /* style_hints_apply     */
  )


/* END */
